[automerger skipped] Merge "Pass whether the local device is an atv device to the native layer to determine whether to include pairing dialogs for justworks and encryption only LE pairing" into oc-dev am: fd9a792974 am: c0d6bd5f56 -s ours am: c4edc2575e -s ours

am skip reason: Change-Id Ib7575ff3d5f7e0c208743eff652440f7947dfed7 with SHA-1 0b2002b32a is in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Bluetooth/+/12308989

Change-Id: I0d1a6ac5c76c5b196c3c04ab9f53a879c3a32a44
diff --git a/.clang-format b/.clang-format
index 178675d..8b28743 100644
--- a/.clang-format
+++ b/.clang-format
@@ -25,8 +25,5 @@
 
 ---
 Language: Java
-BasedOnStyle: Google
-IndentWidth: 4
-ContinuationIndentWidth: 8
-ColumnLimit: 100
-AllowShortIfStatementsOnASingleLine: true
+# Java format is handled by check_style hook
+DisableFormat: true
diff --git a/Android.mk b/Android.mk
index 97390fd..5d3db9a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,37 +1,35 @@
 LOCAL_PATH:= $(call my-dir)
+
+# MAP API module
+
 include $(CLEAR_VARS)
-
 LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := \
-        $(call all-java-files-under, lib/mapapi)
-
+LOCAL_SRC_FILES := $(call all-java-files-under, lib/mapapi)
 LOCAL_MODULE := bluetooth.mapsapi
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
+# Bluetooth APK
+
 include $(CLEAR_VARS)
-
 LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := \
-        $(call all-java-files-under, src) \
-        $(call all-proto-files-under, src)
-
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_PACKAGE_NAME := Bluetooth
+LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_CERTIFICATE := platform
 LOCAL_USE_AAPT2 := true
-
 LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
-LOCAL_JAVA_LIBRARIES := javax.obex telephony-common libprotobuf-java-micro services.net
-LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard bluetooth.mapsapi sap-api-java-static services.net
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-        android-support-v4
-LOCAL_PROTOC_OPTIMIZE_TYPE := micro
+LOCAL_JAVA_LIBRARIES := javax.obex telephony-common services.net
+LOCAL_STATIC_JAVA_LIBRARIES := \
+        com.android.vcard \
+        bluetooth.mapsapi \
+        sap-api-java-static \
+        services.net \
+        libprotobuf-java-lite \
+        bluetooth-protos-lite
 
-LOCAL_REQUIRED_MODULES := bluetooth.default
-
+LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v4
+LOCAL_REQUIRED_MODULES := libbluetooth
 LOCAL_PROGUARD_ENABLED := disabled
-
 include $(BUILD_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a086284..b24064f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -61,11 +61,13 @@
     <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" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
     <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
     <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
     <!-- For PBAP Owner Vcard Info -->
     <uses-permission android:name="android.permission.READ_PROFILE"/>
@@ -317,6 +319,14 @@
         </service>
         <service
             android:process="@string/process"
+            android:name = ".avrcp.AvrcpTargetService"
+            android:enabled = "@bool/profile_supported_avrcp_target" >
+            <intent-filter>
+                <action android:name="android.bluetooth.IBluetoothAvrcp" />
+            </intent-filter>
+        </service>
+        <service
+            android:process="@string/process"
             android:name = ".avrcpcontroller.AvrcpControllerService"
             android:enabled="@bool/profile_supported_avrcp_controller">
             <intent-filter>
@@ -325,10 +335,18 @@
         </service>
         <service
             android:process="@string/process"
-            android:name = ".hid.HidService"
-            android:enabled="@bool/profile_supported_hid">
+            android:name = ".hid.HidHostService"
+            android:enabled="@bool/profile_supported_hid_host">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothInputDevice" />
+                <action android:name="android.bluetooth.IBluetoothHidHost" />
+            </intent-filter>
+        </service>
+        <service
+            android:process="@string/process"
+            android:name = ".hid.HidDeviceService"
+            android:enabled="@bool/profile_supported_hid_device">
+            <intent-filter>
+                <action android:name="android.bluetooth.IBluetoothHidDevice" />
             </intent-filter>
         </service>
         <service
@@ -373,6 +391,13 @@
                 <action android:name="android.bluetooth.IBluetoothPbapClient" />
             </intent-filter>
         </service>
+        <service
+            android:process="@string/process"
+            android:name = ".hearingaid.HearingAidService">
+            <intent-filter>
+                <action android:name="android.bluetooth.IBluetoothHearingAid" />
+            </intent-filter>
+        </service>
         <!-- Authenticator for PBAP account. -->
         <service
             android:process="@string/process"
@@ -386,12 +411,5 @@
                 android:name="android.accounts.AccountAuthenticator"
                 android:resource="@xml/authenticator" />
         </service>
-        <service
-            android:name = ".hid.HidDevService"
-            android:enabled="@bool/profile_supported_hidd">
-            <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothInputHost" />
-            </intent-filter>
-        </service>
     </application>
 </manifest>
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 7fb1636..9c449e0 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,3 +4,8 @@
 [Builtin Hooks]
 clang_format = true
 
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+                  -fw src/com/android/bluetooth/
+                      lib/mapapi/com/android/bluetooth/mapapi/
+                      tests/src/com/android/bluetooth/
diff --git a/jni/Android.bp b/jni/Android.bp
index 73700e8..c0fdd80 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -1,6 +1,8 @@
 cc_library_shared {
     name: "libbluetooth_jni",
+    compile_multilib: "first",
     srcs: [
+        "bluetooth_socket_manager.cc",
         "com_android_bluetooth_btservice_AdapterService.cpp",
         "com_android_bluetooth_hfp.cpp",
         "com_android_bluetooth_hfpclient.cpp",
@@ -8,31 +10,38 @@
         "com_android_bluetooth_a2dp_sink.cpp",
         "com_android_bluetooth_avrcp.cpp",
         "com_android_bluetooth_avrcp_controller.cpp",
-        "com_android_bluetooth_hid.cpp",
-        "com_android_bluetooth_hidd.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",
+        "IUserManager.cc",
+        "permission_helpers.cc",
     ],
+    header_libs: ["libbluetooth_headers"],
     include_dirs: [
         "libnativehelper/include/nativehelper",
         "system/bt/types",
     ],
     shared_libs: [
         "libandroid_runtime",
+        "libbinder",
+        "libbluetooth-binder",
         "libchrome",
         "libnativehelper",
-        "libcutils",
-        "libutils",
         "liblog",
-        "libhardware",
     ],
     static_libs: [
         "libbluetooth-types",
+        "libutils",
+        "libcutils",
     ],
     cflags: [
         "-Wall",
+        "-Werror",
         "-Wextra",
         "-Wno-unused-parameter",
     ],
diff --git a/jni/IUserManager.cc b/jni/IUserManager.cc
new file mode 100644
index 0000000..0e36292
--- /dev/null
+++ b/jni/IUserManager.cc
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "IUserManager"
+#include <binder/Parcel.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <utils/Log.h>
+
+#include "IUserManager.h"
+
+namespace android {
+
+class BpUserManager : public BpInterface<IUserManager> {
+ public:
+  explicit BpUserManager(const sp<IBinder>& impl)
+      : BpInterface<IUserManager>(impl) {}
+  virtual int32_t getCredentialOwnerProfile(int32_t user_id) {
+    Parcel data, reply;
+    data.writeInterfaceToken(IUserManager::getInterfaceDescriptor());
+    data.writeInt32(user_id);
+    status_t rc =
+        remote()->transact(GET_CREDENTIAL_OWNER_PROFILE, data, &reply, 0);
+    if (rc != NO_ERROR) {
+      ALOGE("%s: failed (%d)\n", __func__, rc);
+      return -1;
+    }
+
+    int32_t exception = reply.readExceptionCode();
+    if (exception != 0) {
+      ALOGE("%s: got exception (%d)\n", __func__, exception);
+      return -1;
+    }
+
+    return reply.readInt32();
+  }
+
+  virtual int32_t getProfileParentId(int32_t user_handle) {
+    Parcel data, reply;
+    data.writeInterfaceToken(IUserManager::getInterfaceDescriptor());
+    data.writeInt32(user_handle);
+    status_t rc = remote()->transact(GET_PROFILE_PARENT_ID, data, &reply, 0);
+    if (rc != NO_ERROR) {
+      ALOGE("%s: failed (%d)\n", __func__, rc);
+      return -1;
+    }
+
+    int32_t exception = reply.readExceptionCode();
+    if (exception != 0) {
+      ALOGE("%s: got exception (%d)\n", __func__, exception);
+      return -1;
+    }
+
+    return reply.readInt32();
+  }
+};
+
+IMPLEMENT_META_INTERFACE(UserManager, "android.os.IUserManager");
+
+};  // namespace android
diff --git a/jni/IUserManager.h b/jni/IUserManager.h
new file mode 100644
index 0000000..2164d9a
--- /dev/null
+++ b/jni/IUserManager.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#ifndef IUSERMANAGER_H_
+#define IUSERMANAGER_H_
+
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+#include <inttypes.h>
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+/*
+ * Communication channel to UserManager
+ */
+class IUserManager : public IInterface {
+ public:
+  // must be kept in sync with IUserManager.aidl
+  enum {
+    GET_CREDENTIAL_OWNER_PROFILE = IBinder::FIRST_CALL_TRANSACTION + 0,
+    GET_PROFILE_PARENT_ID,
+  };
+
+  virtual int32_t getCredentialOwnerProfile(int32_t user_id) = 0;
+  virtual int32_t getProfileParentId(int32_t user_handle) = 0;
+
+  DECLARE_META_INTERFACE(UserManager);
+};
+
+};  // namespace android
+
+#endif  // IUSERMANAGER_H_
diff --git a/jni/bluetooth_socket_manager.cc b/jni/bluetooth_socket_manager.cc
new file mode 100644
index 0000000..a63beed
--- /dev/null
+++ b/jni/bluetooth_socket_manager.cc
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "bluetooth_socket_manager.h"
+#include "permission_helpers.h"
+
+#include <base/logging.h>
+#include <binder/IPCThreadState.h>
+
+using ::android::OK;
+using ::android::String8;
+using ::android::binder::Status;
+using ::android::os::ParcelFileDescriptor;
+using ::android::os::ParcelUuid;
+
+namespace android {
+namespace bluetooth {
+
+Status BluetoothSocketManagerBinderServer::connectSocket(
+    const BluetoothDevice& device, int32_t type,
+    const std::unique_ptr<ParcelUuid>& uuid, int32_t port, int32_t flag,
+    std::unique_ptr<ParcelFileDescriptor>* _aidl_return) {
+  if (!isCallerActiveUserOrManagedProfile()) {
+    LOG(WARNING) << "connectSocket() - Not allowed for non-active users";
+    return Status::fromExceptionCode(
+        Status::EX_SECURITY, String8("Not allowed for non-active users"));
+  }
+
+  ENFORCE_PERMISSION(PERMISSION_BLUETOOTH);
+
+  IPCThreadState* ipc = IPCThreadState::self();
+
+  int socket_fd = -1;
+  bt_status_t status = socketInterface->connect(
+      &device.address, (btsock_type_t)type, uuid ? &uuid->uuid : nullptr, port,
+      &socket_fd, flag, ipc->getCallingUid());
+  if (status != BT_STATUS_SUCCESS) {
+    LOG(ERROR) << "Socket connection failed: " << +status;
+    socket_fd = -1;
+  }
+
+  if (socket_fd < 0) {
+    LOG(ERROR) << "Fail to create file descriptor on socket fd";
+    return Status::ok();
+  }
+
+  _aidl_return->reset(new ParcelFileDescriptor());
+  (*_aidl_return)->setFileDescriptor(socket_fd, true);
+  return Status::ok();
+}
+
+Status BluetoothSocketManagerBinderServer::createSocketChannel(
+    int32_t type, const std::unique_ptr<::android::String16>& serviceName,
+    const std::unique_ptr<ParcelUuid>& uuid, int32_t port, int32_t flag,
+    std::unique_ptr<ParcelFileDescriptor>* _aidl_return) {
+  if (!isCallerActiveUserOrManagedProfile()) {
+    LOG(WARNING) << "createSocketChannel() - Not allowed for non-active users";
+    return Status::fromExceptionCode(
+        Status::EX_SECURITY, String8("Not allowed for non-active users"));
+  }
+
+  ENFORCE_PERMISSION(PERMISSION_BLUETOOTH);
+
+  VLOG(1) << __func__ << ": SOCK FLAG=" << flag;
+
+  IPCThreadState* ipc = IPCThreadState::self();
+  int socket_fd = -1;
+
+  const std::string payload_url{};
+
+  bt_status_t status = socketInterface->listen(
+      (btsock_type_t)type,
+      serviceName ? String8{*serviceName}.c_str() : nullptr,
+      uuid ? &uuid->uuid : nullptr, port, &socket_fd, flag,
+      ipc->getCallingUid());
+
+  if (status != BT_STATUS_SUCCESS) {
+    LOG(ERROR) << "Socket listen failed: " << +status;
+    socket_fd = -1;
+  }
+
+  if (socket_fd < 0) {
+    LOG(ERROR) << "Failed to create file descriptor on socket fd";
+    return Status::ok();
+  }
+
+  _aidl_return->reset(new ParcelFileDescriptor());
+  (*_aidl_return)->setFileDescriptor(socket_fd, true);
+  return Status::ok();
+}
+
+Status BluetoothSocketManagerBinderServer::requestMaximumTxDataLength(
+    const BluetoothDevice& device) {
+  if (!isCallerActiveUserOrManagedProfile()) {
+    LOG(WARNING) << __func__ << ": Not allowed for non-active users";
+    return Status::fromExceptionCode(
+        Status::EX_SECURITY, String8("Not allowed for non-active users"));
+  }
+
+  ENFORCE_PERMISSION(PERMISSION_BLUETOOTH);
+
+  VLOG(1) << __func__;
+
+  socketInterface->request_max_tx_data_length(device.address);
+  return Status::ok();
+}
+
+}  // namespace bluetooth
+}  // namespace android
diff --git a/jni/bluetooth_socket_manager.h b/jni/bluetooth_socket_manager.h
new file mode 100644
index 0000000..e732dcc
--- /dev/null
+++ b/jni/bluetooth_socket_manager.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <android/bluetooth/BnBluetoothSocketManager.h>
+#include <android/bluetooth/IBluetoothSocketManager.h>
+#include <base/macros.h>
+#include <binder/IBinder.h>
+#include <binder/IInterface.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_sock.h>
+
+namespace android {
+namespace bluetooth {
+
+using ::android::binder::Status;
+
+class BluetoothSocketManagerBinderServer : public BnBluetoothSocketManager {
+ public:
+  explicit BluetoothSocketManagerBinderServer(
+      const btsock_interface_t* socketInterface)
+      : socketInterface(socketInterface) {}
+  ~BluetoothSocketManagerBinderServer() override = default;
+
+  Status connectSocket(
+      const BluetoothDevice& device, int32_t type,
+      const std::unique_ptr<::android::os::ParcelUuid>& uuid, int32_t port,
+      int32_t flag,
+      std::unique_ptr<::android::os::ParcelFileDescriptor>* _aidl_return);
+
+  Status createSocketChannel(
+      int32_t type, const std::unique_ptr<::android::String16>& serviceName,
+      const std::unique_ptr<::android::os::ParcelUuid>& uuid, int32_t port,
+      int32_t flag,
+      std::unique_ptr<::android::os::ParcelFileDescriptor>* _aidl_return)
+      override;
+
+  Status requestMaximumTxDataLength(const BluetoothDevice& device);
+
+ private:
+  const btsock_interface_t* socketInterface;
+  DISALLOW_COPY_AND_ASSIGN(BluetoothSocketManagerBinderServer);
+};
+}  // namespace bluetooth
+}  // namespace android
\ No newline at end of file
diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h
index 50b4968..9f136a0 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -18,7 +18,7 @@
 #ifndef COM_ANDROID_BLUETOOTH_H
 #define COM_ANDROID_BLUETOOTH_H
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
 #include "hardware/bluetooth.h"
@@ -81,11 +81,13 @@
 
 int register_com_android_bluetooth_avrcp(JNIEnv* env);
 
+int register_com_android_bluetooth_avrcp_target(JNIEnv* env);
+
 int register_com_android_bluetooth_avrcp_controller(JNIEnv* env);
 
-int register_com_android_bluetooth_hid(JNIEnv* env);
+int register_com_android_bluetooth_hid_host(JNIEnv* env);
 
-int register_com_android_bluetooth_hidd(JNIEnv* env);
+int register_com_android_bluetooth_hid_device(JNIEnv* env);
 
 int register_com_android_bluetooth_hdp(JNIEnv* env);
 
@@ -95,6 +97,7 @@
 
 int register_com_android_bluetooth_sdp (JNIEnv* env);
 
+int register_com_android_bluetooth_hearing_aid(JNIEnv* env);
 }
 
 #endif /* COM_ANDROID_BLUETOOTH_H */
diff --git a/jni/com_android_bluetooth_a2dp.cpp b/jni/com_android_bluetooth_a2dp.cpp
index 26c9359..126405a3 100644
--- a/jni/com_android_bluetooth_a2dp.cpp
+++ b/jni/com_android_bluetooth_a2dp.cpp
@@ -24,6 +24,7 @@
 #include "utils/Log.h"
 
 #include <string.h>
+#include <shared_mutex>
 
 namespace android {
 static jmethodID method_onConnectionStateChanged;
@@ -44,54 +45,65 @@
   jmethodID getCodecSpecific4;
 } android_bluetooth_BluetoothCodecConfig;
 
-static const btav_source_interface_t* sBluetoothA2dpInterface = NULL;
-static jobject mCallbacksObj = NULL;
+static const btav_source_interface_t* sBluetoothA2dpInterface = nullptr;
+static std::shared_timed_mutex interface_mutex;
 
-static void bta2dp_connection_state_callback(btav_connection_state_t state,
-                                             RawAddress* bd_addr) {
+static jobject mCallbacksObj = nullptr;
+static std::shared_timed_mutex callbacks_mutex;
+
+static void bta2dp_connection_state_callback(const RawAddress& bd_addr,
+                                             btav_connection_state_t state) {
   ALOGI("%s", __func__);
+
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
+  if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) 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: Fail to new jbyteArray bd addr", __func__);
     return;
   }
 
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+  sCallbackEnv->SetByteArrayRegion(
+      addr.get(), 0, sizeof(RawAddress),
+      reinterpret_cast<const jbyte*>(bd_addr.address));
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
-                               (jint)state, addr.get());
+                               addr.get(), (jint)state);
 }
 
-static void bta2dp_audio_state_callback(btav_audio_state_t state,
-                                        RawAddress* bd_addr) {
+static void bta2dp_audio_state_callback(const RawAddress& bd_addr,
+                                        btav_audio_state_t state) {
   ALOGI("%s", __func__);
+
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
+  if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) 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: Fail to new jbyteArray bd addr", __func__);
     return;
   }
 
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+  sCallbackEnv->SetByteArrayRegion(
+      addr.get(), 0, sizeof(RawAddress),
+      reinterpret_cast<const jbyte*>(bd_addr.address));
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged,
-                               (jint)state, addr.get());
+                               addr.get(), (jint)state);
 }
 
 static void bta2dp_audio_config_callback(
-    btav_a2dp_codec_config_t codec_config,
+    const RawAddress& bd_addr, btav_a2dp_codec_config_t codec_config,
     std::vector<btav_a2dp_codec_config_t> codecs_local_capabilities,
     std::vector<btav_a2dp_codec_config_t> codecs_selectable_capabilities) {
   ALOGI("%s", __func__);
+
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return;
+  if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
 
   jobject codecConfigObj = sCallbackEnv->NewObject(
       android_bluetooth_BluetoothCodecConfig.clazz,
@@ -106,7 +118,7 @@
   jsize i = 0;
   jobjectArray local_capabilities_array = sCallbackEnv->NewObjectArray(
       (jsize)codecs_local_capabilities.size(),
-      android_bluetooth_BluetoothCodecConfig.clazz, NULL);
+      android_bluetooth_BluetoothCodecConfig.clazz, nullptr);
   for (auto const& cap : codecs_local_capabilities) {
     jobject capObj = sCallbackEnv->NewObject(
         android_bluetooth_BluetoothCodecConfig.clazz,
@@ -122,7 +134,7 @@
   i = 0;
   jobjectArray selectable_capabilities_array = sCallbackEnv->NewObjectArray(
       (jsize)codecs_selectable_capabilities.size(),
-      android_bluetooth_BluetoothCodecConfig.clazz, NULL);
+      android_bluetooth_BluetoothCodecConfig.clazz, nullptr);
   for (auto const& cap : codecs_selectable_capabilities) {
     jobject capObj = sCallbackEnv->NewObject(
         android_bluetooth_BluetoothCodecConfig.clazz,
@@ -136,9 +148,19 @@
     sCallbackEnv->DeleteLocalRef(capObj);
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCodecConfigChanged,
-                               codecConfigObj, local_capabilities_array,
-                               selectable_capabilities_array);
+  ScopedLocalRef<jbyteArray> addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(RawAddress::kLength));
+  if (!addr.get()) {
+    ALOGE("%s: Fail to new jbyteArray bd addr", __func__);
+    return;
+  }
+  sCallbackEnv->SetByteArrayRegion(
+      addr.get(), 0, RawAddress::kLength,
+      reinterpret_cast<const jbyte*>(bd_addr.address));
+
+  sCallbackEnv->CallVoidMethod(
+      mCallbacksObj, method_onCodecConfigChanged, addr.get(), codecConfigObj,
+      local_capabilities_array, selectable_capabilities_array);
 }
 
 static btav_source_callbacks_t sBluetoothA2dpCallbacks = {
@@ -171,15 +193,16 @@
       jniBluetoothCodecConfigClass, "getCodecSpecific4", "()J");
 
   method_onConnectionStateChanged =
-      env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V");
+      env->GetMethodID(clazz, "onConnectionStateChanged", "([BI)V");
 
   method_onAudioStateChanged =
-      env->GetMethodID(clazz, "onAudioStateChanged", "(I[B)V");
+      env->GetMethodID(clazz, "onAudioStateChanged", "([BI)V");
 
-  method_onCodecConfigChanged = env->GetMethodID(
-      clazz, "onCodecConfigChanged",
-      "(Landroid/bluetooth/BluetoothCodecConfig;[Landroid/bluetooth/"
-      "BluetoothCodecConfig;[Landroid/bluetooth/BluetoothCodecConfig;)V");
+  method_onCodecConfigChanged =
+      env->GetMethodID(clazz, "onCodecConfigChanged",
+                       "([BLandroid/bluetooth/BluetoothCodecConfig;"
+                       "[Landroid/bluetooth/BluetoothCodecConfig;"
+                       "[Landroid/bluetooth/BluetoothCodecConfig;)V");
 
   ALOGI("%s: succeeds", __func__);
 }
@@ -194,7 +217,7 @@
     if (jcodecConfig == nullptr) continue;
     if (!env->IsInstanceOf(jcodecConfig,
                            android_bluetooth_BluetoothCodecConfig.clazz)) {
-      ALOGE("Invalid BluetoothCodecConfig instance");
+      ALOGE("%s: Invalid BluetoothCodecConfig instance", __func__);
       continue;
     }
     jint codecType = env->CallIntMethod(
@@ -236,92 +259,107 @@
 }
 
 static void initNative(JNIEnv* env, jobject object,
+                       jint maxConnectedAudioDevices,
                        jobjectArray codecConfigArray) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
   const bt_interface_t* btInf = getBluetoothInterface();
-  if (btInf == NULL) {
-    ALOGE("Bluetooth module is not loaded");
+  if (btInf == nullptr) {
+    ALOGE("%s: Bluetooth module is not loaded", __func__);
     return;
   }
 
-  if (sBluetoothA2dpInterface != NULL) {
-    ALOGW("Cleaning up A2DP Interface before initializing...");
+  if (sBluetoothA2dpInterface != nullptr) {
+    ALOGW("%s: Cleaning up A2DP Interface before initializing...", __func__);
     sBluetoothA2dpInterface->cleanup();
-    sBluetoothA2dpInterface = NULL;
+    sBluetoothA2dpInterface = nullptr;
   }
 
-  if (mCallbacksObj != NULL) {
-    ALOGW("Cleaning up A2DP callback object");
+  if (mCallbacksObj != nullptr) {
+    ALOGW("%s: Cleaning up A2DP callback object", __func__);
     env->DeleteGlobalRef(mCallbacksObj);
-    mCallbacksObj = NULL;
+    mCallbacksObj = nullptr;
   }
 
-  if ((mCallbacksObj = env->NewGlobalRef(object)) == NULL) {
-    ALOGE("Failed to allocate Global Ref for A2DP Callbacks");
+  if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) {
+    ALOGE("%s: Failed to allocate Global Ref for A2DP Callbacks", __func__);
     return;
   }
 
   android_bluetooth_BluetoothCodecConfig.clazz = (jclass)env->NewGlobalRef(
       env->FindClass("android/bluetooth/BluetoothCodecConfig"));
   if (android_bluetooth_BluetoothCodecConfig.clazz == nullptr) {
-    ALOGE("Failed to allocate Global Ref for BluetoothCodecConfig class");
+    ALOGE("%s: Failed to allocate Global Ref for BluetoothCodecConfig class",
+          __func__);
     return;
   }
 
   sBluetoothA2dpInterface =
       (btav_source_interface_t*)btInf->get_profile_interface(
           BT_PROFILE_ADVANCED_AUDIO_ID);
-  if (sBluetoothA2dpInterface == NULL) {
-    ALOGE("Failed to get Bluetooth A2DP Interface");
+  if (sBluetoothA2dpInterface == nullptr) {
+    ALOGE("%s: Failed to get Bluetooth A2DP Interface", __func__);
     return;
   }
 
   std::vector<btav_a2dp_codec_config_t> codec_priorities =
       prepareCodecPreferences(env, object, codecConfigArray);
 
-  bt_status_t status =
-      sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks, codec_priorities);
+  bt_status_t status = sBluetoothA2dpInterface->init(
+      &sBluetoothA2dpCallbacks, maxConnectedAudioDevices, codec_priorities);
   if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed to initialize Bluetooth A2DP, status: %d", status);
-    sBluetoothA2dpInterface = NULL;
+    ALOGE("%s: Failed to initialize Bluetooth A2DP, status: %d", __func__,
+          status);
+    sBluetoothA2dpInterface = nullptr;
     return;
   }
 }
 
 static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
   const bt_interface_t* btInf = getBluetoothInterface();
-  if (btInf == NULL) {
-    ALOGE("Bluetooth module is not loaded");
+  if (btInf == nullptr) {
+    ALOGE("%s: Bluetooth module is not loaded", __func__);
     return;
   }
 
-  if (sBluetoothA2dpInterface != NULL) {
+  if (sBluetoothA2dpInterface != nullptr) {
     sBluetoothA2dpInterface->cleanup();
-    sBluetoothA2dpInterface = NULL;
+    sBluetoothA2dpInterface = nullptr;
   }
 
   env->DeleteGlobalRef(android_bluetooth_BluetoothCodecConfig.clazz);
   android_bluetooth_BluetoothCodecConfig.clazz = nullptr;
 
-  if (mCallbacksObj != NULL) {
+  if (mCallbacksObj != nullptr) {
     env->DeleteGlobalRef(mCallbacksObj);
-    mCallbacksObj = NULL;
+    mCallbacksObj = nullptr;
   }
 }
 
 static jboolean connectA2dpNative(JNIEnv* env, jobject object,
                                   jbyteArray address) {
   ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
-  if (!sBluetoothA2dpInterface) return JNI_FALSE;
+  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, NULL);
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
 
-  bt_status_t status = sBluetoothA2dpInterface->connect((RawAddress*)addr);
+  RawAddress bd_addr;
+  bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
+  bt_status_t status = sBluetoothA2dpInterface->connect(bd_addr);
   if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed HF connection, status: %d", status);
+    ALOGE("%s: Failed A2DP connection, status: %d", __func__, status);
   }
   env->ReleaseByteArrayElements(address, addr, 0);
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
@@ -329,51 +367,98 @@
 
 static jboolean disconnectA2dpNative(JNIEnv* env, jobject object,
                                      jbyteArray address) {
-  if (!sBluetoothA2dpInterface) return JNI_FALSE;
+  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, NULL);
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
 
-  bt_status_t status = sBluetoothA2dpInterface->disconnect((RawAddress*)addr);
+  RawAddress bd_addr;
+  bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
+  bt_status_t status = sBluetoothA2dpInterface->disconnect(bd_addr);
   if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed HF disconnection, status: %d", status);
+    ALOGE("%s: Failed A2DP disconnection, 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);
+  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));
+  }
+  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);
   }
   env->ReleaseByteArrayElements(address, addr, 0);
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
 static jboolean setCodecConfigPreferenceNative(JNIEnv* env, jobject object,
+                                               jbyteArray address,
                                                jobjectArray codecConfigArray) {
-  if (!sBluetoothA2dpInterface) return JNI_FALSE;
+  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);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  RawAddress bd_addr;
+  bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
   std::vector<btav_a2dp_codec_config_t> codec_preferences =
       prepareCodecPreferences(env, object, codecConfigArray);
 
-  bt_status_t status = sBluetoothA2dpInterface->config_codec(codec_preferences);
+  bt_status_t status =
+      sBluetoothA2dpInterface->config_codec(bd_addr, codec_preferences);
   if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed codec configuration, status: %d", status);
+    ALOGE("%s: Failed codec configuration, status: %d", __func__, status);
   }
+  env->ReleaseByteArrayElements(address, addr, 0);
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void*)classInitNative},
-    {"initNative", "([Landroid/bluetooth/BluetoothCodecConfig;)V",
+    {"initNative", "(I[Landroid/bluetooth/BluetoothCodecConfig;)V",
      (void*)initNative},
     {"cleanupNative", "()V", (void*)cleanupNative},
     {"connectA2dpNative", "([B)Z", (void*)connectA2dpNative},
     {"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative},
+    {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative},
     {"setCodecConfigPreferenceNative",
-     "([Landroid/bluetooth/BluetoothCodecConfig;)Z",
+     "([B[Landroid/bluetooth/BluetoothCodecConfig;)Z",
      (void*)setCodecConfigPreferenceNative},
 };
 
 int register_com_android_bluetooth_a2dp(JNIEnv* env) {
-  return jniRegisterNativeMethods(env,
-                                  "com/android/bluetooth/a2dp/A2dpStateMachine",
-                                  sMethods, NELEM(sMethods));
+  return jniRegisterNativeMethods(
+      env, "com/android/bluetooth/a2dp/A2dpNativeInterface", sMethods,
+      NELEM(sMethods));
 }
 }
diff --git a/jni/com_android_bluetooth_a2dp_sink.cpp b/jni/com_android_bluetooth_a2dp_sink.cpp
index 989cd6f..50c5087 100644
--- a/jni/com_android_bluetooth_a2dp_sink.cpp
+++ b/jni/com_android_bluetooth_a2dp_sink.cpp
@@ -33,8 +33,8 @@
 static const btav_sink_interface_t* sBluetoothA2dpInterface = NULL;
 static jobject mCallbacksObj = NULL;
 
-static void bta2dp_connection_state_callback(btav_connection_state_t state,
-                                             RawAddress* bd_addr) {
+static void bta2dp_connection_state_callback(const RawAddress& bd_addr,
+                                             btav_connection_state_t state) {
   ALOGI("%s", __func__);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -47,13 +47,13 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (const jbyte*)bd_addr.address);
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
-                               (jint)state, addr.get());
+                               addr.get(), (jint)state);
 }
 
-static void bta2dp_audio_state_callback(btav_audio_state_t state,
-                                        RawAddress* bd_addr) {
+static void bta2dp_audio_state_callback(const RawAddress& bd_addr,
+                                        btav_audio_state_t state) {
   ALOGI("%s", __func__);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -66,12 +66,12 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (const jbyte*)bd_addr.address);
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged,
-                               (jint)state, addr.get());
+                               addr.get(), (jint)state);
 }
 
-static void bta2dp_audio_config_callback(RawAddress* bd_addr,
+static void bta2dp_audio_config_callback(const RawAddress& bd_addr,
                                          uint32_t sample_rate,
                                          uint8_t channel_count) {
   ALOGI("%s", __func__);
@@ -86,7 +86,7 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (const jbyte*)bd_addr.address);
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioConfigChanged,
                                addr.get(), (jint)sample_rate,
                                (jint)channel_count);
@@ -99,10 +99,10 @@
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
   method_onConnectionStateChanged =
-      env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V");
+      env->GetMethodID(clazz, "onConnectionStateChanged", "([BI)V");
 
   method_onAudioStateChanged =
-      env->GetMethodID(clazz, "onAudioStateChanged", "(I[B)V");
+      env->GetMethodID(clazz, "onAudioStateChanged", "([BI)V");
 
   method_onAudioConfigChanged =
       env->GetMethodID(clazz, "onAudioConfigChanged", "([BII)V");
@@ -177,7 +177,9 @@
     return JNI_FALSE;
   }
 
-  bt_status_t status = sBluetoothA2dpInterface->connect((RawAddress*)addr);
+  RawAddress bd_addr;
+  bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
+  bt_status_t status = sBluetoothA2dpInterface->connect(bd_addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed HF connection, status: %d", status);
   }
@@ -195,7 +197,9 @@
     return JNI_FALSE;
   }
 
-  bt_status_t status = sBluetoothA2dpInterface->disconnect((RawAddress*)addr);
+  RawAddress bd_addr;
+  bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
+  bt_status_t status = sBluetoothA2dpInterface->disconnect(bd_addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed HF disconnection, status: %d", status);
   }
diff --git a/jni/com_android_bluetooth_avrcp.cpp b/jni/com_android_bluetooth_avrcp.cpp
index d365b53..1eb3553 100644
--- a/jni/com_android_bluetooth_avrcp.cpp
+++ b/jni/com_android_bluetooth_avrcp.cpp
@@ -23,6 +23,7 @@
 #include "hardware/bt_rc.h"
 #include "utils/Log.h"
 
+#include <inttypes.h>
 #include <string.h>
 
 namespace android {
@@ -57,7 +58,7 @@
 
 static void cleanup_items(btrc_folder_items_t* p_items, int numItems);
 
-static void btavrcp_remote_features_callback(RawAddress* bd_addr,
+static void btavrcp_remote_features_callback(const RawAddress& bd_addr,
                                              btrc_remote_features_t features) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -75,13 +76,13 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (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(RawAddress* bd_addr) {
+static void btavrcp_get_play_status_callback(const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -98,13 +99,13 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (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,
-                                              RawAddress* bd_addr) {
+                                              const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -130,14 +131,14 @@
   sCallbackEnv->SetIntArrayRegion(attrs.get(), 0, num_attr, (jint*)p_attrs);
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (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,
-                                                   RawAddress* bd_addr) {
+                                                   const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -154,13 +155,13 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (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,
-                                           RawAddress* bd_addr) {
+                                           const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -177,14 +178,14 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (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,
-                                                 RawAddress* bd_addr) {
+                                                 const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -200,14 +201,14 @@
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (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,
-                                                  RawAddress* bd_addr) {
+                                                  const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -224,13 +225,13 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (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,
-                                                RawAddress* bd_addr) {
+                                                const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -245,7 +246,7 @@
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)bd_addr.address);
 
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_setBrowsedPlayerCallback,
                                addr.get(), (jint)player_id);
@@ -253,7 +254,7 @@
 
 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, RawAddress* bd_addr) {
+    uint32_t* p_attr_ids, const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -272,7 +273,7 @@
   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);
+                                   (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)) {
@@ -293,7 +294,7 @@
 }
 
 static void btavrcp_change_path_callback(uint8_t direction, uint8_t* folder_uid,
-                                         RawAddress* bd_addr) {
+                                         const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -317,7 +318,7 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)bd_addr.address);
   sCallbackEnv->SetByteArrayRegion(
       attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)folder_uid);
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_changePathCallback,
@@ -328,7 +329,7 @@
                                            uint16_t uid_counter,
                                            uint8_t num_attr,
                                            btrc_media_attr_t* p_attrs,
-                                           RawAddress* bd_addr) {
+                                           const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -359,7 +360,7 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (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);
@@ -370,7 +371,8 @@
 }
 
 static void btavrcp_play_item_callback(uint8_t scope, uint16_t uid_counter,
-                                       uint8_t* uid, RawAddress* bd_addr) {
+                                       uint8_t* uid,
+                                       const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -393,7 +395,7 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)bd_addr.address);
   sCallbackEnv->SetByteArrayRegion(
       attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_playItemCallback,
@@ -402,7 +404,7 @@
 }
 
 static void btavrcp_get_total_num_items_callback(uint8_t scope,
-                                                 RawAddress* bd_addr) {
+                                                 const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -418,13 +420,13 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (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, RawAddress* bd_addr) {
+                                    uint8_t* p_str, const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -447,7 +449,7 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)bd_addr.address);
   sCallbackEnv->SetByteArrayRegion(attrs.get(), 0, str_len * sizeof(uint8_t),
                                    (jbyte*)p_str);
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_searchCallback, addr.get(),
@@ -456,7 +458,7 @@
 
 static void btavrcp_add_to_play_list_callback(uint8_t scope, uint8_t* uid,
                                               uint16_t uid_counter,
-                                              RawAddress* bd_addr) {
+                                              const RawAddress& bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   if (!mCallbacksObj) {
@@ -479,7 +481,7 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)bd_addr.address);
   sCallbackEnv->SetByteArrayRegion(
       attrs.get(), 0, sizeof(uint8_t) * BTRC_UID_SIZE, (jbyte*)uid);
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_addToPlayListCallback,
@@ -628,9 +630,11 @@
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
 
   bt_status_t status = sBluetoothAvrcpInterface->get_play_status_rsp(
-      (RawAddress*)addr, (btrc_play_status_t)playStatus, songLen, songPos);
+      rawAddress, (btrc_play_status_t)playStatus, songLen, songPos);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed get_play_status_rsp, status: %d", status);
   }
@@ -692,9 +696,10 @@
     return JNI_FALSE;
   }
 
-  RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status =
-      sBluetoothAvrcpInterface->get_element_attr_rsp(btAddr, numAttr, pAttrs);
+  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);
   }
@@ -755,10 +760,11 @@
       break;
     }
   }
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
 
-  RawAddress* btAddr = (RawAddress*)addr;
   bt_status_t status = sBluetoothAvrcpInterface->get_item_attr_rsp(
-      btAddr, (btrc_status_t)rspStatus, numAttr, pAttrs);
+      rawAddress, (btrc_status_t)rspStatus, numAttr, pAttrs);
   if (status != BT_STATUS_SUCCESS)
     ALOGE("Failed get_item_attr_rsp, status: %d", status);
 
@@ -812,7 +818,7 @@
     uid = uid + (trk[uid_idx] << (BTRC_UID_SIZE - 1 - uid_idx));
   }
 
-  ALOGV("%s: Sending track change notification: %d -> %llu", __func__, type,
+  ALOGV("%s: Sending track change notification: %d -> %" PRIu64, __func__, type,
         uid);
 
   bt_status_t status = sBluetoothAvrcpInterface->register_notification_rsp(
@@ -1024,7 +1030,7 @@
 
   RawAddress* btAddr = (RawAddress*)addr;
   bt_status_t status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(
-      btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
+      *btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed get_folder_items_list_rsp, status: %d", status);
   }
@@ -1159,7 +1165,7 @@
 
   RawAddress* btAddr = (RawAddress*)addr;
   bt_status_t status = sBluetoothAvrcpInterface->get_folder_items_list_rsp(
-      btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
+      *btAddr, (btrc_status_t)rspStatus, uidCounter, numItems, p_items);
   if (status != BT_STATUS_SUCCESS)
     ALOGE("Failed get_folder_items_list_rsp, status: %d", status);
 
@@ -1197,7 +1203,7 @@
 
   RawAddress* btAddr = (RawAddress*)addr;
   bt_status_t status = sBluetoothAvrcpInterface->set_addressed_player_rsp(
-      btAddr, (btrc_status_t)rspStatus);
+      *btAddr, (btrc_status_t)rspStatus);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed set_addressed_player_rsp, status: %d", status);
   }
@@ -1251,7 +1257,7 @@
   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,
+      *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);
@@ -1282,7 +1288,7 @@
   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);
+      *btAddr, (btrc_status_t)rspStatus, (uint32_t)nItems);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed change_path_rsp, status: %d", status);
   }
@@ -1308,7 +1314,8 @@
   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);
+      *btAddr, (btrc_status_t)rspStatus, (uint32_t)uidCounter,
+      (uint32_t)nItems);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed search_rsp, status: %d", status);
   }
@@ -1332,8 +1339,8 @@
   }
 
   RawAddress* btAddr = (RawAddress*)addr;
-  bt_status_t status =
-      sBluetoothAvrcpInterface->play_item_rsp(btAddr, (btrc_status_t)rspStatus);
+  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);
   }
@@ -1359,7 +1366,8 @@
   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);
+      *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);
   }
@@ -1383,7 +1391,7 @@
 
   RawAddress* btAddr = (RawAddress*)addr;
   bt_status_t status = sBluetoothAvrcpInterface->add_to_now_playing_rsp(
-      btAddr, (btrc_status_t)rspStatus);
+      *btAddr, (btrc_status_t)rspStatus);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed add_to_now_playing_rsp, status: %d", status);
   }
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
index 40982ee..7710a91 100644
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -53,8 +53,8 @@
 static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = NULL;
 static jobject sCallbacksObj = NULL;
 
-static void btavrcp_passthrough_response_callback(RawAddress* bd_addr, int id,
-                                                  int pressed) {
+static void btavrcp_passthrough_response_callback(const RawAddress& bd_addr,
+                                                  int id, int pressed) {
   ALOGI("%s: id: %d, pressed: %d", __func__, id, pressed);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -67,7 +67,7 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handlePassthroughRsp,
                                (jint)id, (jint)pressed, addr.get());
 }
@@ -82,7 +82,7 @@
 }
 
 static void btavrcp_connection_state_callback(bool rc_connect, bool br_connect,
-                                              RawAddress* bd_addr) {
+                                              const RawAddress& bd_addr) {
   ALOGI("%s: conn state: rc: %d br: %d", __func__, rc_connect, br_connect);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -95,13 +95,14 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)bd_addr.address);
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_onConnectionStateChanged,
                                (jboolean)rc_connect, (jboolean)br_connect,
                                addr.get());
 }
 
-static void btavrcp_get_rcfeatures_callback(RawAddress* bd_addr, int features) {
+static void btavrcp_get_rcfeatures_callback(const RawAddress& bd_addr,
+                                            int features) {
   ALOGV("%s", __func__);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -114,13 +115,13 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_getRcFeatures, addr.get(),
                                (jint)features);
 }
 
 static void btavrcp_setplayerapplicationsetting_rsp_callback(
-    RawAddress* bd_addr, uint8_t accepted) {
+    const RawAddress& bd_addr, uint8_t accepted) {
   ALOGV("%s", __func__);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -133,14 +134,15 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_setplayerappsettingrsp,
                                addr.get(), (jint)accepted);
 }
 
 static void btavrcp_playerapplicationsetting_callback(
-    RawAddress* bd_addr, uint8_t num_attr, btrc_player_app_attr_t* app_attrs,
-    uint8_t num_ext_attr, btrc_player_app_ext_attr_t* ext_attrs) {
+    const RawAddress& bd_addr, uint8_t num_attr,
+    btrc_player_app_attr_t* app_attrs, uint8_t num_ext_attr,
+    btrc_player_app_ext_attr_t* ext_attrs) {
   ALOGI("%s", __func__);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -152,7 +154,7 @@
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
   /* TODO ext attrs
    * Flattening defined attributes: <id,num_values,values[]>
    */
@@ -187,7 +189,7 @@
 }
 
 static void btavrcp_playerapplicationsetting_changed_callback(
-    RawAddress* bd_addr, btrc_player_settings_t* p_vals) {
+    const RawAddress& bd_addr, const btrc_player_settings_t& vals) {
   ALOGI("%s", __func__);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -199,9 +201,9 @@
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
 
-  int arraylen = p_vals->num_attr * 2;
+  int arraylen = vals.num_attr * 2;
   ScopedLocalRef<jbyteArray> playerattribs(
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(arraylen));
   if (!playerattribs.get()) {
@@ -211,12 +213,12 @@
   /*
    * Flatening format: <id,val>
    */
-  for (int i = 0, k = 0; (i < p_vals->num_attr) && (k < arraylen); i++) {
+  for (int i = 0, k = 0; (i < vals.num_attr) && (k < arraylen); i++) {
     sCallbackEnv->SetByteArrayRegion(playerattribs.get(), k, 1,
-                                     (jbyte*)&(p_vals->attr_ids[i]));
+                                     (jbyte*)&(vals.attr_ids[i]));
     k++;
     sCallbackEnv->SetByteArrayRegion(playerattribs.get(), k, 1,
-                                     (jbyte*)&(p_vals->attr_values[i]));
+                                     (jbyte*)&(vals.attr_values[i]));
     k++;
   }
   sCallbackEnv->CallVoidMethod(sCallbacksObj,
@@ -224,7 +226,7 @@
                                playerattribs.get(), (jint)arraylen);
 }
 
-static void btavrcp_set_abs_vol_cmd_callback(RawAddress* bd_addr,
+static void btavrcp_set_abs_vol_cmd_callback(const RawAddress& bd_addr,
                                              uint8_t abs_vol, uint8_t label) {
   ALOGI("%s", __func__);
   CallbackEnv sCallbackEnv(__func__);
@@ -238,13 +240,13 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleSetAbsVolume,
                                addr.get(), (jbyte)abs_vol, (jbyte)label);
 }
 
-static void btavrcp_register_notification_absvol_callback(RawAddress* bd_addr,
-                                                          uint8_t label) {
+static void btavrcp_register_notification_absvol_callback(
+    const RawAddress& bd_addr, uint8_t label) {
   ALOGI("%s", __func__);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -257,13 +259,13 @@
   }
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
   sCallbackEnv->CallVoidMethod(sCallbacksObj,
                                method_handleRegisterNotificationAbsVol,
                                addr.get(), (jbyte)label);
 }
 
-static void btavrcp_track_changed_callback(RawAddress* bd_addr,
+static void btavrcp_track_changed_callback(const RawAddress& bd_addr,
                                            uint8_t num_attr,
                                            btrc_element_attr_val_t* p_attrs) {
   /*
@@ -288,7 +290,7 @@
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
 
   jclass strclazz = sCallbackEnv->FindClass("java/lang/String");
   ScopedLocalRef<jobjectArray> stringArray(
@@ -317,7 +319,7 @@
                                stringArray.get());
 }
 
-static void btavrcp_play_position_changed_callback(RawAddress* bd_addr,
+static void btavrcp_play_position_changed_callback(const RawAddress& bd_addr,
                                                    uint32_t song_len,
                                                    uint32_t song_pos) {
   ALOGI("%s", __func__);
@@ -331,13 +333,13 @@
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplaypositionchanged,
                                addr.get(), (jint)(song_len), (jint)song_pos);
 }
 
 static void btavrcp_play_status_changed_callback(
-    RawAddress* bd_addr, btrc_play_status_t play_status) {
+    const RawAddress& bd_addr, btrc_play_status_t play_status) {
   ALOGI("%s", __func__);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -349,13 +351,13 @@
     return;
   }
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
+                                   (jbyte*)&bd_addr.address);
   sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplaystatuschanged,
                                addr.get(), (jbyte)play_status);
 }
 
 static void btavrcp_get_folder_items_callback(
-    RawAddress* bd_addr, btrc_status_t status,
+    const RawAddress& bd_addr, btrc_status_t status,
     const btrc_folder_items_t* folder_items, uint8_t count) {
   /* Folder items are list of items that can be either BTRC_ITEM_PLAYER
    * BTRC_ITEM_MEDIA, BTRC_ITEM_FOLDER. Here we translate them to their java
@@ -545,7 +547,8 @@
   }
 }
 
-static void btavrcp_change_path_callback(RawAddress* bd_addr, uint8_t count) {
+static void btavrcp_change_path_callback(const RawAddress& bd_addr,
+                                         uint32_t count) {
   ALOGI("%s count %d", __func__, count);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -554,7 +557,7 @@
                                (jint)count);
 }
 
-static void btavrcp_set_browsed_player_callback(RawAddress* bd_addr,
+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);
@@ -565,7 +568,7 @@
                                (jint)num_items, (jint)depth);
 }
 
-static void btavrcp_set_addressed_player_callback(RawAddress* bd_addr,
+static void btavrcp_set_addressed_player_callback(const RawAddress& bd_addr,
                                                   uint8_t status) {
   ALOGI("%s status %d", __func__, status);
 
@@ -738,8 +741,10 @@
     return JNI_FALSE;
   }
 
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
   bt_status_t status = sBluetoothAvrcpInterface->send_pass_through_cmd(
-      (RawAddress*)addr, (uint8_t)key_code, (uint8_t)key_state);
+      rawAddress, (uint8_t)key_code, (uint8_t)key_state);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending passthru command, status: %d", status);
   }
@@ -763,9 +768,11 @@
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
 
   bt_status_t status = sBluetoothAvrcpInterface->send_group_navigation_cmd(
-      (RawAddress*)addr, (uint8_t)key_code, (uint8_t)key_state);
+      rawAddress, (uint8_t)key_code, (uint8_t)key_state);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending Grp Navigation command, status: %d", status);
   }
@@ -810,9 +817,11 @@
     pAttrs[i] = (uint8_t)attr[i];
     pAttrsVal[i] = (uint8_t)attr_val[i];
   }
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
 
   bt_status_t status = sBluetoothAvrcpInterface->set_player_app_setting_cmd(
-      (RawAddress*)addr, (uint8_t)num_attrib, pAttrs, pAttrsVal);
+      rawAddress, (uint8_t)num_attrib, pAttrs, pAttrsVal);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending setPlAppSettValNative command, status: %d", status);
   }
@@ -834,8 +843,11 @@
   }
 
   ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
+
   bt_status_t status = sBluetoothAvrcpInterface->set_volume_rsp(
-      (RawAddress*)addr, (uint8_t)abs_vol, (uint8_t)label);
+      rawAddress, (uint8_t)abs_vol, (uint8_t)label);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending sendAbsVolRspNative command, status: %d", status);
   }
@@ -853,8 +865,11 @@
     return;
   }
   ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
+
   bt_status_t status = sBluetoothAvrcpInterface->register_abs_vol_rsp(
-      (RawAddress*)addr, (btrc_notification_type_t)rsp_type, (uint8_t)abs_vol,
+      rawAddress, (btrc_notification_type_t)rsp_type, (uint8_t)abs_vol,
       (uint8_t)label);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending sendRegisterAbsVolRspNative command, status: %d",
@@ -873,8 +888,11 @@
     return;
   }
   ALOGV("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
+
   bt_status_t status =
-      sBluetoothAvrcpInterface->get_playback_state_cmd((RawAddress*)addr);
+      sBluetoothAvrcpInterface->get_playback_state_cmd(rawAddress);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending getPlaybackStateNative command, status: %d", status);
   }
@@ -882,8 +900,7 @@
 }
 
 static void getNowPlayingListNative(JNIEnv* env, jobject object,
-                                    jbyteArray address, jbyte start,
-                                    jbyte items) {
+                                    jbyteArray address, jint start, jint end) {
   if (!sBluetoothAvrcpInterface) return;
   jbyte* addr = env->GetByteArrayElements(address, NULL);
   if (!addr) {
@@ -891,8 +908,11 @@
     return;
   }
   ALOGV("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
+
   bt_status_t status = sBluetoothAvrcpInterface->get_now_playing_list_cmd(
-      (RawAddress*)addr, (uint8_t)start, (uint8_t)items);
+      rawAddress, start, end);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending getNowPlayingListNative command, status: %d", status);
   }
@@ -900,7 +920,7 @@
 }
 
 static void getFolderListNative(JNIEnv* env, jobject object, jbyteArray address,
-                                jbyte start, jbyte items) {
+                                jint start, jint end) {
   if (!sBluetoothAvrcpInterface) return;
   jbyte* addr = env->GetByteArrayElements(address, NULL);
   if (!addr) {
@@ -908,8 +928,11 @@
     return;
   }
   ALOGV("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
-  bt_status_t status = sBluetoothAvrcpInterface->get_folder_list_cmd(
-      (RawAddress*)addr, (uint8_t)start, (uint8_t)items);
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
+
+  bt_status_t status =
+      sBluetoothAvrcpInterface->get_folder_list_cmd(rawAddress, start, end);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending getFolderListNative command, status: %d", status);
   }
@@ -917,7 +940,7 @@
 }
 
 static void getPlayerListNative(JNIEnv* env, jobject object, jbyteArray address,
-                                jbyte start, jbyte items) {
+                                jint start, jint end) {
   if (!sBluetoothAvrcpInterface) return;
   jbyte* addr = env->GetByteArrayElements(address, NULL);
   if (!addr) {
@@ -925,9 +948,11 @@
     return;
   }
   ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
 
-  bt_status_t status = sBluetoothAvrcpInterface->get_player_list_cmd(
-      (RawAddress*)addr, (uint8_t)start, (uint8_t)items);
+  bt_status_t status =
+      sBluetoothAvrcpInterface->get_player_list_cmd(rawAddress, start, end);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending getPlayerListNative command, status: %d", status);
   }
@@ -951,9 +976,11 @@
   }
 
   ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
 
   bt_status_t status = sBluetoothAvrcpInterface->change_folder_path_cmd(
-      (RawAddress*)addr, (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);
   }
@@ -968,10 +995,12 @@
     jniThrowIOException(env, EINVAL);
     return;
   }
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
 
   ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
   bt_status_t status = sBluetoothAvrcpInterface->set_browsed_player_cmd(
-      (RawAddress*)addr, (uint16_t)id);
+      rawAddress, (uint16_t)id);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending setBrowsedPlayerNative command, status: %d", status);
   }
@@ -986,10 +1015,12 @@
     jniThrowIOException(env, EINVAL);
     return;
   }
+  RawAddress rawAddress;
+  rawAddress.FromOctets((uint8_t*)addr);
 
   ALOGI("%s: sBluetoothAvrcpInterface: %p", __func__, sBluetoothAvrcpInterface);
   bt_status_t status = sBluetoothAvrcpInterface->set_addressed_player_cmd(
-      (RawAddress*)addr, (uint16_t)id);
+      rawAddress, (uint16_t)id);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending setAddressedPlayerNative command, status: %d",
           status);
@@ -1011,10 +1042,12 @@
     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*)addr, (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);
   }
@@ -1035,9 +1068,9 @@
     {"sendRegisterAbsVolRspNative", "([BBII)V",
      (void*)sendRegisterAbsVolRspNative},
     {"getPlaybackStateNative", "([B)V", (void*)getPlaybackStateNative},
-    {"getNowPlayingListNative", "([BBB)V", (void*)getNowPlayingListNative},
-    {"getFolderListNative", "([BBB)V", (void*)getFolderListNative},
-    {"getPlayerListNative", "([BBB)V", (void*)getPlayerListNative},
+    {"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},
     {"setBrowsedPlayerNative", "([BI)V", (void*)setBrowsedPlayerNative},
diff --git a/jni/com_android_bluetooth_avrcp_target.cpp b/jni/com_android_bluetooth_avrcp_target.cpp
new file mode 100644
index 0000000..8ac04e4
--- /dev/null
+++ b/jni/com_android_bluetooth_avrcp_target.cpp
@@ -0,0 +1,826 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NewAvrcpTargetJni"
+
+#include <base/bind.h>
+#include <map>
+#include <mutex>
+#include <shared_mutex>
+#include <vector>
+
+#include "android_runtime/AndroidRuntime.h"
+#include "avrcp.h"
+#include "com_android_bluetooth.h"
+#include "utils/Log.h"
+
+using namespace bluetooth::avrcp;
+
+namespace android {
+
+// Static Variables
+static MediaCallbacks* mServiceCallbacks;
+static ServiceInterface* sServiceInterface;
+static jobject mJavaInterface;
+static std::shared_timed_mutex interface_mutex;
+static std::shared_timed_mutex callbacks_mutex;
+
+// Forward Declarations
+static void sendMediaKeyEvent(int, KeyState);
+static std::string getCurrentMediaId();
+static SongInfo getSongInfo();
+static PlayStatus getCurrentPlayStatus();
+static std::vector<SongInfo> getNowPlayingList();
+static uint16_t getCurrentPlayerId();
+static std::vector<MediaPlayerInfo> getMediaPlayerList();
+using SetBrowsedPlayerCb = MediaInterface::SetBrowsedPlayerCallback;
+static void setBrowsedPlayer(uint16_t player_id, SetBrowsedPlayerCb);
+using GetFolderItemsCb = MediaInterface::FolderItemsCallback;
+static void getFolderItems(uint16_t player_id, std::string media_id,
+                           GetFolderItemsCb cb);
+static void playItem(uint16_t player_id, bool now_playing,
+                     std::string media_id);
+static void setActiveDevice(const RawAddress& address);
+
+static void volumeDeviceConnected(const RawAddress& address);
+static void volumeDeviceConnected(
+    const RawAddress& address,
+    ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb cb);
+static void volumeDeviceDisconnected(const RawAddress& address);
+static void setVolume(int8_t volume);
+
+// Local Variables
+// TODO (apanicke): Use a map here to store the callback in order to
+// support multi-browsing
+SetBrowsedPlayerCb set_browsed_player_cb;
+using map_entry = std::pair<std::string, GetFolderItemsCb>;
+std::map<std::string, GetFolderItemsCb> get_folder_items_cb_map;
+std::map<RawAddress, ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb>
+    volumeCallbackMap;
+
+// TODO (apanicke): In the future, this interface should guarantee that
+// all calls happen on the JNI Thread. Right now this is very difficult
+// as it is hard to get a handle on the JNI thread from here.
+class AvrcpMediaInterfaceImpl : public MediaInterface {
+ public:
+  void SendKeyEvent(uint8_t key, KeyState state) {
+    sendMediaKeyEvent(key, state);
+  }
+
+  void GetSongInfo(SongInfoCallback cb) override {
+    auto info = getSongInfo();
+    cb.Run(info);
+  }
+
+  void GetPlayStatus(PlayStatusCallback cb) override {
+    auto status = getCurrentPlayStatus();
+    cb.Run(status);
+  }
+
+  void GetNowPlayingList(NowPlayingCallback cb) override {
+    auto curr_song_id = getCurrentMediaId();
+    auto now_playing_list = getNowPlayingList();
+    cb.Run(curr_song_id, std::move(now_playing_list));
+  }
+
+  void GetMediaPlayerList(MediaListCallback cb) override {
+    uint16_t current_player = getCurrentPlayerId();
+    auto player_list = getMediaPlayerList();
+    cb.Run(current_player, std::move(player_list));
+  }
+
+  void GetFolderItems(uint16_t player_id, std::string media_id,
+                      FolderItemsCallback folder_cb) override {
+    getFolderItems(player_id, media_id, folder_cb);
+  }
+
+  void SetBrowsedPlayer(uint16_t player_id,
+                        SetBrowsedPlayerCallback browse_cb) override {
+    setBrowsedPlayer(player_id, browse_cb);
+  }
+
+  void RegisterUpdateCallback(MediaCallbacks* callback) override {
+    // TODO (apanicke): Allow multiple registrations in the future
+    mServiceCallbacks = callback;
+  }
+
+  void UnregisterUpdateCallback(MediaCallbacks* callback) override {
+    mServiceCallbacks = nullptr;
+  }
+
+  void PlayItem(uint16_t player_id, bool now_playing,
+                std::string media_id) override {
+    playItem(player_id, now_playing, media_id);
+  }
+
+  void SetActiveDevice(const RawAddress& address) override {
+    setActiveDevice(address);
+  }
+};
+static AvrcpMediaInterfaceImpl mAvrcpInterface;
+
+class VolumeInterfaceImpl : public VolumeInterface {
+ public:
+  void DeviceConnected(const RawAddress& bdaddr) override {
+    volumeDeviceConnected(bdaddr);
+  }
+
+  void DeviceConnected(const RawAddress& bdaddr, VolumeChangedCb cb) override {
+    volumeDeviceConnected(bdaddr, cb);
+  }
+
+  void DeviceDisconnected(const RawAddress& bdaddr) override {
+    volumeDeviceDisconnected(bdaddr);
+  }
+
+  void SetVolume(int8_t volume) override { setVolume(volume); }
+};
+static VolumeInterfaceImpl mVolumeInterface;
+
+static jmethodID method_getCurrentSongInfo;
+static jmethodID method_getPlaybackStatus;
+static jmethodID method_sendMediaKeyEvent;
+
+static jmethodID method_getCurrentMediaId;
+static jmethodID method_getNowPlayingList;
+
+static jmethodID method_setBrowsedPlayer;
+static jmethodID method_getCurrentPlayerId;
+static jmethodID method_getMediaPlayerList;
+static jmethodID method_getFolderItemsRequest;
+static jmethodID method_playItem;
+
+static jmethodID method_setActiveDevice;
+
+static jmethodID method_volumeDeviceConnected;
+static jmethodID method_volumeDeviceDisconnected;
+
+static jmethodID method_setVolume;
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+  method_getCurrentSongInfo = env->GetMethodID(
+      clazz, "getCurrentSongInfo", "()Lcom/android/bluetooth/avrcp/Metadata;");
+
+  method_getPlaybackStatus = env->GetMethodID(
+      clazz, "getPlayStatus", "()Lcom/android/bluetooth/avrcp/PlayStatus;");
+
+  method_sendMediaKeyEvent =
+      env->GetMethodID(clazz, "sendMediaKeyEvent", "(IZ)V");
+
+  method_getCurrentMediaId =
+      env->GetMethodID(clazz, "getCurrentMediaId", "()Ljava/lang/String;");
+
+  method_getNowPlayingList =
+      env->GetMethodID(clazz, "getNowPlayingList", "()Ljava/util/List;");
+
+  method_getCurrentPlayerId =
+      env->GetMethodID(clazz, "getCurrentPlayerId", "()I");
+
+  method_getMediaPlayerList =
+      env->GetMethodID(clazz, "getMediaPlayerList", "()Ljava/util/List;");
+
+  method_setBrowsedPlayer = env->GetMethodID(clazz, "setBrowsedPlayer", "(I)V");
+
+  method_getFolderItemsRequest = env->GetMethodID(
+      clazz, "getFolderItemsRequest", "(ILjava/lang/String;)V");
+
+  method_playItem =
+      env->GetMethodID(clazz, "playItem", "(IZLjava/lang/String;)V");
+
+  method_setActiveDevice =
+      env->GetMethodID(clazz, "setActiveDevice", "(Ljava/lang/String;)V");
+
+  // Volume Management functions
+  method_volumeDeviceConnected =
+      env->GetMethodID(clazz, "deviceConnected", "(Ljava/lang/String;Z)V");
+
+  method_volumeDeviceDisconnected =
+      env->GetMethodID(clazz, "deviceDisconnected", "(Ljava/lang/String;)V");
+
+  method_setVolume = env->GetMethodID(clazz, "setVolume", "(I)V");
+
+  ALOGI("%s: AvrcpTargetJni initialized!", __func__);
+}
+
+static void initNative(JNIEnv* env, jobject object) {
+  ALOGD("%s", __func__);
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+  mJavaInterface = env->NewGlobalRef(object);
+
+  sServiceInterface = getBluetoothInterface()->get_avrcp_service();
+  sServiceInterface->Init(&mAvrcpInterface, &mVolumeInterface);
+}
+
+static void sendMediaUpdateNative(JNIEnv* env, jobject object,
+                                  jboolean metadata, jboolean state,
+                                  jboolean queue) {
+  ALOGD("%s", __func__);
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  if (mServiceCallbacks == nullptr) {
+    ALOGW("%s: Service not loaded.", __func__);
+    return;
+  }
+
+  mServiceCallbacks->SendMediaUpdate(metadata == JNI_TRUE, state == JNI_TRUE,
+                                     queue == JNI_TRUE);
+}
+
+static void sendFolderUpdateNative(JNIEnv* env, jobject object,
+                                   jboolean available_players,
+                                   jboolean addressed_player, jboolean uids) {
+  ALOGD("%s", __func__);
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  if (mServiceCallbacks == nullptr) {
+    ALOGW("%s: Service not loaded.", __func__);
+    return;
+  }
+
+  mServiceCallbacks->SendFolderUpdate(available_players == JNI_TRUE,
+                                      addressed_player == JNI_TRUE,
+                                      uids == JNI_TRUE);
+}
+
+static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+  get_folder_items_cb_map.clear();
+  volumeCallbackMap.clear();
+
+  sServiceInterface->Cleanup();
+  env->DeleteGlobalRef(mJavaInterface);
+  mJavaInterface = nullptr;
+  mServiceCallbacks = nullptr;
+  sServiceInterface = nullptr;
+}
+
+jboolean connectDeviceNative(JNIEnv* env, jobject object, jstring address) {
+  ALOGD("%s", __func__);
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  if (mServiceCallbacks == nullptr) {
+    ALOGW("%s: Service not loaded.", __func__);
+    return JNI_FALSE;
+  }
+
+  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 JNI_FALSE;
+
+  return sServiceInterface->ConnectDevice(bdaddr) == true ? JNI_TRUE
+                                                          : JNI_FALSE;
+}
+
+jboolean disconnectDeviceNative(JNIEnv* env, jobject object, jstring address) {
+  ALOGD("%s", __func__);
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  if (mServiceCallbacks == nullptr) {
+    ALOGW("%s: Service not loaded.", __func__);
+    return JNI_FALSE;
+  }
+
+  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 JNI_FALSE;
+
+  return sServiceInterface->DisconnectDevice(bdaddr) == true ? JNI_TRUE
+                                                             : JNI_FALSE;
+}
+
+static void sendMediaKeyEvent(int key, KeyState state) {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return;
+  sCallbackEnv->CallVoidMethod(
+      mJavaInterface, method_sendMediaKeyEvent, key,
+      state == KeyState::PUSHED ? JNI_TRUE : JNI_FALSE);
+}
+
+static SongInfo getSongInfoFromJavaObj(JNIEnv* env, jobject metadata) {
+  SongInfo info;
+
+  if (metadata == nullptr) return info;
+
+  jclass class_metadata = env->GetObjectClass(metadata);
+  jfieldID field_mediaId =
+      env->GetFieldID(class_metadata, "mediaId", "Ljava/lang/String;");
+  jfieldID field_title =
+      env->GetFieldID(class_metadata, "title", "Ljava/lang/String;");
+  jfieldID field_artist =
+      env->GetFieldID(class_metadata, "artist", "Ljava/lang/String;");
+  jfieldID field_album =
+      env->GetFieldID(class_metadata, "album", "Ljava/lang/String;");
+  jfieldID field_trackNum =
+      env->GetFieldID(class_metadata, "trackNum", "Ljava/lang/String;");
+  jfieldID field_numTracks =
+      env->GetFieldID(class_metadata, "numTracks", "Ljava/lang/String;");
+  jfieldID field_genre =
+      env->GetFieldID(class_metadata, "genre", "Ljava/lang/String;");
+  jfieldID field_playingTime =
+      env->GetFieldID(class_metadata, "duration", "Ljava/lang/String;");
+
+  jstring jstr = (jstring)env->GetObjectField(metadata, field_mediaId);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.media_id = std::string(value);
+    env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
+  }
+
+  jstr = (jstring)env->GetObjectField(metadata, field_title);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.attributes.insert(
+        AttributeEntry(Attribute::TITLE, std::string(value)));
+    env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
+  }
+
+  jstr = (jstring)env->GetObjectField(metadata, field_artist);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.attributes.insert(
+        AttributeEntry(Attribute::ARTIST_NAME, std::string(value)));
+    env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
+  }
+
+  jstr = (jstring)env->GetObjectField(metadata, field_album);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.attributes.insert(
+        AttributeEntry(Attribute::ALBUM_NAME, std::string(value)));
+    env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
+  }
+
+  jstr = (jstring)env->GetObjectField(metadata, field_trackNum);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.attributes.insert(
+        AttributeEntry(Attribute::TRACK_NUMBER, std::string(value)));
+    env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
+  }
+
+  jstr = (jstring)env->GetObjectField(metadata, field_numTracks);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.attributes.insert(
+        AttributeEntry(Attribute::TOTAL_NUMBER_OF_TRACKS, std::string(value)));
+    env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
+  }
+
+  jstr = (jstring)env->GetObjectField(metadata, field_genre);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.attributes.insert(
+        AttributeEntry(Attribute::GENRE, std::string(value)));
+    env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
+  }
+
+  jstr = (jstring)env->GetObjectField(metadata, field_playingTime);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.attributes.insert(
+        AttributeEntry(Attribute::PLAYING_TIME, std::string(value)));
+    env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
+  }
+
+  return info;
+}
+
+static FolderInfo getFolderInfoFromJavaObj(JNIEnv* env, jobject folder) {
+  FolderInfo info;
+
+  jclass class_folder = env->GetObjectClass(folder);
+  jfieldID field_mediaId =
+      env->GetFieldID(class_folder, "mediaId", "Ljava/lang/String;");
+  jfieldID field_isPlayable = env->GetFieldID(class_folder, "isPlayable", "Z");
+  jfieldID field_name =
+      env->GetFieldID(class_folder, "title", "Ljava/lang/String;");
+
+  jstring jstr = (jstring)env->GetObjectField(folder, field_mediaId);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.media_id = std::string(value);
+    env->ReleaseStringUTFChars(jstr, value);
+  }
+
+  info.is_playable = env->GetBooleanField(folder, field_isPlayable) == JNI_TRUE;
+
+  jstr = (jstring)env->GetObjectField(folder, field_name);
+  if (jstr != nullptr) {
+    const char* value = env->GetStringUTFChars(jstr, nullptr);
+    info.name = std::string(value);
+    env->ReleaseStringUTFChars(jstr, value);
+  }
+
+  return info;
+}
+
+static SongInfo getSongInfo() {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return SongInfo();
+
+  jobject metadata =
+      sCallbackEnv->CallObjectMethod(mJavaInterface, method_getCurrentSongInfo);
+  return getSongInfoFromJavaObj(sCallbackEnv.get(), metadata);
+}
+
+static PlayStatus getCurrentPlayStatus() {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return PlayStatus();
+
+  PlayStatus status;
+  jobject playStatus =
+      sCallbackEnv->CallObjectMethod(mJavaInterface, method_getPlaybackStatus);
+
+  if (playStatus == nullptr) {
+    ALOGE("%s: Got a null play status", __func__);
+    return status;
+  }
+
+  jclass class_playStatus = sCallbackEnv->GetObjectClass(playStatus);
+  jfieldID field_position =
+      sCallbackEnv->GetFieldID(class_playStatus, "position", "J");
+  jfieldID field_duration =
+      sCallbackEnv->GetFieldID(class_playStatus, "duration", "J");
+  jfieldID field_state =
+      sCallbackEnv->GetFieldID(class_playStatus, "state", "B");
+
+  status.position = sCallbackEnv->GetLongField(playStatus, field_position);
+  status.duration = sCallbackEnv->GetLongField(playStatus, field_duration);
+  status.state = (PlayState)sCallbackEnv->GetByteField(playStatus, field_state);
+
+  return status;
+}
+
+static std::string getCurrentMediaId() {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return "";
+
+  jstring media_id = (jstring)sCallbackEnv->CallObjectMethod(
+      mJavaInterface, method_getCurrentMediaId);
+  if (media_id == nullptr) {
+    ALOGE("%s: Got a null media ID", __func__);
+    return "";
+  }
+
+  const char* value = sCallbackEnv->GetStringUTFChars(media_id, nullptr);
+  std::string ret(value);
+  sCallbackEnv->ReleaseStringUTFChars(media_id, value);
+  return ret;
+}
+
+static std::vector<SongInfo> getNowPlayingList() {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return std::vector<SongInfo>();
+
+  jobject song_list =
+      sCallbackEnv->CallObjectMethod(mJavaInterface, method_getNowPlayingList);
+  if (song_list == nullptr) {
+    ALOGE("%s: Got a null now playing list", __func__);
+    return std::vector<SongInfo>();
+  }
+
+  jclass class_list = sCallbackEnv->GetObjectClass(song_list);
+  jmethodID method_get =
+      sCallbackEnv->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;");
+  jmethodID method_size = sCallbackEnv->GetMethodID(class_list, "size", "()I");
+
+  auto size = sCallbackEnv->CallIntMethod(song_list, method_size);
+  if (size == 0) return std::vector<SongInfo>();
+  std::vector<SongInfo> ret;
+  for (int i = 0; i < size; i++) {
+    jobject song = sCallbackEnv->CallObjectMethod(song_list, method_get, i);
+    ret.push_back(getSongInfoFromJavaObj(sCallbackEnv.get(), song));
+    sCallbackEnv->DeleteLocalRef(song);
+  }
+
+  return ret;
+}
+
+static uint16_t getCurrentPlayerId() {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return 0u;
+
+  jint id =
+      sCallbackEnv->CallIntMethod(mJavaInterface, method_getCurrentPlayerId);
+
+  return (static_cast<int>(id) & 0xFFFF);
+}
+
+static std::vector<MediaPlayerInfo> getMediaPlayerList() {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface)
+    return std::vector<MediaPlayerInfo>();
+
+  jobject player_list = (jobject)sCallbackEnv->CallObjectMethod(
+      mJavaInterface, method_getMediaPlayerList);
+
+  if (player_list == nullptr) {
+    ALOGE("%s: Got a null media player list", __func__);
+    return std::vector<MediaPlayerInfo>();
+  }
+
+  jclass class_list = sCallbackEnv->GetObjectClass(player_list);
+  jmethodID method_get =
+      sCallbackEnv->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;");
+  jmethodID method_size = sCallbackEnv->GetMethodID(class_list, "size", "()I");
+
+  jint list_size = sCallbackEnv->CallIntMethod(player_list, method_size);
+  if (list_size == 0) {
+    return std::vector<MediaPlayerInfo>();
+  }
+
+  jclass class_playerInfo = sCallbackEnv->GetObjectClass(
+      sCallbackEnv->CallObjectMethod(player_list, method_get, 0));
+  jfieldID field_playerId =
+      sCallbackEnv->GetFieldID(class_playerInfo, "id", "I");
+  jfieldID field_name =
+      sCallbackEnv->GetFieldID(class_playerInfo, "name", "Ljava/lang/String;");
+  jfieldID field_browsable =
+      sCallbackEnv->GetFieldID(class_playerInfo, "browsable", "Z");
+
+  std::vector<MediaPlayerInfo> ret_list;
+  for (jsize i = 0; i < list_size; i++) {
+    jobject player = sCallbackEnv->CallObjectMethod(player_list, method_get, i);
+
+    MediaPlayerInfo temp;
+    temp.id = sCallbackEnv->GetIntField(player, field_playerId);
+
+    jstring jstr = (jstring)sCallbackEnv->GetObjectField(player, field_name);
+    if (jstr != nullptr) {
+      const char* value = sCallbackEnv->GetStringUTFChars(jstr, nullptr);
+      temp.name = std::string(value);
+      sCallbackEnv->ReleaseStringUTFChars(jstr, value);
+      sCallbackEnv->DeleteLocalRef(jstr);
+    }
+
+    temp.browsing_supported =
+        sCallbackEnv->GetBooleanField(player, field_browsable) == JNI_TRUE
+            ? true
+            : false;
+
+    ret_list.push_back(std::move(temp));
+    sCallbackEnv->DeleteLocalRef(player);
+  }
+
+  return ret_list;
+}
+
+static void setBrowsedPlayer(uint16_t player_id, SetBrowsedPlayerCb cb) {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return;
+
+  set_browsed_player_cb = cb;
+  sCallbackEnv->CallVoidMethod(mJavaInterface, method_setBrowsedPlayer,
+                               player_id);
+}
+
+static void setBrowsedPlayerResponseNative(JNIEnv* env, jobject object,
+                                           jint player_id, jboolean success,
+                                           jstring root_id, jint num_items) {
+  ALOGD("%s", __func__);
+
+  std::string root;
+  if (root_id != nullptr) {
+    const char* value = env->GetStringUTFChars(root_id, nullptr);
+    root = std::string(value);
+    env->ReleaseStringUTFChars(root_id, value);
+  }
+
+  set_browsed_player_cb.Run(success == JNI_TRUE, root, num_items);
+}
+
+static void getFolderItemsResponseNative(JNIEnv* env, jobject object,
+                                         jstring parent_id, jobject list) {
+  ALOGD("%s", __func__);
+
+  std::string id;
+  if (parent_id != nullptr) {
+    const char* value = env->GetStringUTFChars(parent_id, nullptr);
+    id = std::string(value);
+    env->ReleaseStringUTFChars(parent_id, value);
+  }
+
+  // TODO (apanicke): Right now browsing will fail on a second device if two
+  // devices browse the same folder. Use a MultiMap to fix this behavior so
+  // that both callbacks can be handled with one lookup if a request comes
+  // for a folder that is already trying to be looked at.
+  if (get_folder_items_cb_map.find(id) == get_folder_items_cb_map.end()) {
+    ALOGE("Could not find response callback for the request of \"%s\"",
+          id.c_str());
+    return;
+  }
+
+  auto callback = get_folder_items_cb_map.find(id)->second;
+  get_folder_items_cb_map.erase(id);
+
+  if (list == nullptr) {
+    ALOGE("%s: Got a null get folder items response list", __func__);
+    callback.Run(std::vector<ListItem>());
+    return;
+  }
+
+  jclass class_list = env->GetObjectClass(list);
+  jmethodID method_get =
+      env->GetMethodID(class_list, "get", "(I)Ljava/lang/Object;");
+  jmethodID method_size = env->GetMethodID(class_list, "size", "()I");
+
+  jint list_size = env->CallIntMethod(list, method_size);
+  if (list_size == 0) {
+    callback.Run(std::vector<ListItem>());
+    return;
+  }
+
+  jclass class_listItem =
+      env->GetObjectClass(env->CallObjectMethod(list, method_get, 0));
+  jfieldID field_isFolder = env->GetFieldID(class_listItem, "isFolder", "Z");
+  jfieldID field_folder = env->GetFieldID(
+      class_listItem, "folder", "Lcom/android/bluetooth/avrcp/Folder;");
+  jfieldID field_song = env->GetFieldID(
+      class_listItem, "song", "Lcom/android/bluetooth/avrcp/Metadata;");
+
+  std::vector<ListItem> ret_list;
+  for (jsize i = 0; i < list_size; i++) {
+    jobject item = env->CallObjectMethod(list, method_get, i);
+
+    bool is_folder = env->GetBooleanField(item, field_isFolder) == JNI_TRUE;
+
+    if (is_folder) {
+      ListItem temp = {ListItem::FOLDER,
+                       getFolderInfoFromJavaObj(
+                           env, env->GetObjectField(item, field_folder)),
+                       SongInfo()};
+
+      ret_list.push_back(temp);
+    } else {
+      ListItem temp = {
+          ListItem::SONG, FolderInfo(),
+          getSongInfoFromJavaObj(env, env->GetObjectField(item, field_song))};
+
+      ret_list.push_back(temp);
+    }
+    env->DeleteLocalRef(item);
+  }
+
+  callback.Run(std::move(ret_list));
+}
+
+static void getFolderItems(uint16_t player_id, std::string media_id,
+                           GetFolderItemsCb cb) {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return;
+
+  // TODO (apanicke): Fix a potential media_id collision if two media players
+  // use the same media_id scheme or two devices browse the same content.
+  get_folder_items_cb_map.insert(map_entry(media_id, cb));
+
+  jstring j_media_id = sCallbackEnv->NewStringUTF(media_id.c_str());
+  sCallbackEnv->CallVoidMethod(mJavaInterface, method_getFolderItemsRequest,
+                               player_id, j_media_id);
+}
+
+static void playItem(uint16_t player_id, bool now_playing,
+                     std::string media_id) {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return;
+
+  jstring j_media_id = sCallbackEnv->NewStringUTF(media_id.c_str());
+  sCallbackEnv->CallVoidMethod(mJavaInterface, method_playItem, player_id,
+                               now_playing ? JNI_TRUE : JNI_FALSE, j_media_id);
+}
+
+static void setActiveDevice(const RawAddress& address) {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return;
+
+  jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str());
+  sCallbackEnv->CallVoidMethod(mJavaInterface, method_setActiveDevice,
+                               j_bdaddr);
+}
+
+static void volumeDeviceConnected(const RawAddress& address) {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return;
+
+  jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str());
+  sCallbackEnv->CallVoidMethod(mJavaInterface, method_volumeDeviceConnected,
+                               j_bdaddr, JNI_FALSE);
+}
+
+static void volumeDeviceConnected(
+    const RawAddress& address,
+    ::bluetooth::avrcp::VolumeInterface::VolumeChangedCb cb) {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return;
+
+  volumeCallbackMap.emplace(address, cb);
+
+  jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str());
+  sCallbackEnv->CallVoidMethod(mJavaInterface, method_volumeDeviceConnected,
+                               j_bdaddr, JNI_TRUE);
+}
+
+static void volumeDeviceDisconnected(const RawAddress& address) {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return;
+
+  volumeCallbackMap.erase(address);
+
+  jstring j_bdaddr = sCallbackEnv->NewStringUTF(address.ToString().c_str());
+  sCallbackEnv->CallVoidMethod(mJavaInterface, method_volumeDeviceDisconnected,
+                               j_bdaddr);
+}
+
+static void sendVolumeChangedNative(JNIEnv* env, jobject object, jint volume) {
+  ALOGD("%s", __func__);
+  for (const auto& cb : volumeCallbackMap) {
+    cb.second.Run(volume & 0x7F);
+  }
+}
+
+static void setVolume(int8_t volume) {
+  ALOGD("%s", __func__);
+  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid() || !mJavaInterface) return;
+
+  sCallbackEnv->CallVoidMethod(mJavaInterface, method_setVolume, volume);
+}
+
+static JNINativeMethod sMethods[] = {
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initNative", "()V", (void*)initNative},
+    {"sendMediaUpdateNative", "(ZZZ)V", (void*)sendMediaUpdateNative},
+    {"sendFolderUpdateNative", "(ZZZ)V", (void*)sendFolderUpdateNative},
+    {"setBrowsedPlayerResponseNative", "(IZLjava/lang/String;I)V",
+     (void*)setBrowsedPlayerResponseNative},
+    {"getFolderItemsResponseNative", "(Ljava/lang/String;Ljava/util/List;)V",
+     (void*)getFolderItemsResponseNative},
+    {"cleanupNative", "()V", (void*)cleanupNative},
+    {"connectDeviceNative", "(Ljava/lang/String;)Z",
+     (void*)connectDeviceNative},
+    {"disconnectDeviceNative", "(Ljava/lang/String;)Z",
+     (void*)disconnectDeviceNative},
+    {"sendVolumeChangedNative", "(I)V", (void*)sendVolumeChangedNative},
+};
+
+int register_com_android_bluetooth_avrcp_target(JNIEnv* env) {
+  return jniRegisterNativeMethods(
+      env, "com/android/bluetooth/avrcp/AvrcpNativeInterface", sMethods,
+      NELEM(sMethods));
+}
+
+}  // namespace android
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index ecda5ac..f88a675 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -17,12 +17,19 @@
 #define LOG_TAG "BluetoothServiceJni"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
+#include "bluetooth_socket_manager.h"
 #include "com_android_bluetooth.h"
-#include "cutils/properties.h"
 #include "hardware/bt_sock.h"
+#include "permission_helpers.h"
 #include "utils/Log.h"
 #include "utils/misc.h"
 
+#include <android_util_Binder.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <cutils/properties.h>
+#include <dlfcn.h>
+#include <errno.h>
 #include <pthread.h>
 #include <string.h>
 
@@ -30,6 +37,13 @@
 #include <sys/prctl.h>
 #include <sys/stat.h>
 
+#include <hardware/bluetooth.h>
+#include <mutex>
+
+using base::StringPrintf;
+using bluetooth::Uuid;
+using android::bluetooth::BluetoothSocketManagerBinderServer;
+
 namespace android {
 // OOB_LE_BD_ADDR_SIZE is 6 bytes addres + 1 byte address type
 #define OOB_LE_BD_ADDR_SIZE 7
@@ -64,6 +78,11 @@
 static jobject sJniCallbacksObj;
 static jfieldID sJniCallbacksField;
 
+namespace {
+android::sp<BluetoothSocketManagerBinderServer> sSocketManager = NULL;
+std::mutex sSocketManagerMutex;
+}
+
 const bt_interface_t* getBluetoothInterface() { return sBluetoothInterface; }
 
 JNIEnv* getCallbackEnv() { return callbackEnv; }
@@ -581,6 +600,45 @@
     acquire_wake_lock_callout, release_wake_lock_callout,
 };
 
+#define PROPERTY_BT_LIBRARY_NAME "ro.bluetooth.library_name"
+#define DEFAULT_BT_LIBRARY_NAME "libbluetooth.so"
+
+int hal_util_load_bt_library(const bt_interface_t** interface) {
+  const char* sym = BLUETOOTH_INTERFACE_STRING;
+  bt_interface_t* itf = nullptr;
+
+  // The library name is not set by default, so the preset library name is used.
+  char path[PROPERTY_VALUE_MAX] = "";
+  property_get(PROPERTY_BT_LIBRARY_NAME, path, DEFAULT_BT_LIBRARY_NAME);
+  void* handle = dlopen(path, RTLD_NOW);
+  if (!handle) {
+    const char* err_str = dlerror();
+    LOG(ERROR) << __func__ << ": failed to load Bluetooth library, error="
+               << (err_str ? err_str : "error unknown");
+    goto error;
+  }
+
+  // Get the address of the bt_interface_t.
+  itf = (bt_interface_t*)dlsym(handle, sym);
+  if (!itf) {
+    LOG(ERROR) << __func__ << ": failed to load symbol from Bluetooth library "
+               << sym;
+    goto error;
+  }
+
+  // Success.
+  LOG(INFO) << __func__ << " loaded HAL: btinterface=" << itf
+            << ", handle=" << handle;
+  *interface = itf;
+  return 0;
+
+error:
+  *interface = NULL;
+  if (handle) dlclose(handle);
+
+  return -EINVAL;
+}
+
 static void classInitNative(JNIEnv* env, jclass clazz) {
   jclass jniUidTrafficClass = env->FindClass("android/bluetooth/UidTraffic");
   android_bluetooth_UidTraffic.constructor =
@@ -622,25 +680,7 @@
   method_energyInfo = env->GetMethodID(
       clazz, "energyInfoCallback", "(IIJJJJ[Landroid/bluetooth/UidTraffic;)V");
 
-  char value[PROPERTY_VALUE_MAX];
-  property_get("bluetooth.mock_stack", value, "");
-
-  const char* id =
-      (strcmp(value, "1") ? BT_STACK_MODULE_ID : BT_STACK_TEST_MODULE_ID);
-
-  hw_module_t* module;
-  int err = hw_get_module(id, (hw_module_t const**)&module);
-
-  if (err == 0) {
-    hw_device_t* abstraction;
-    err = module->methods->open(module, id, &abstraction);
-    if (err == 0) {
-      bluetooth_module_t* btStack = (bluetooth_module_t*)abstraction;
-      sBluetoothInterface = btStack->get_bluetooth_interface();
-    } else {
-      ALOGE("Error while opening Bluetooth library");
-    }
-  } else {
+  if (hal_util_load_bt_library((bt_interface_t const**)&sBluetoothInterface)) {
     ALOGE("No Bluetooth Library found");
   }
 }
@@ -691,10 +731,24 @@
   sBluetoothInterface->cleanup();
   ALOGI("%s: return from cleanup", __func__);
 
-  env->DeleteGlobalRef(sJniCallbacksObj);
-  env->DeleteGlobalRef(sJniAdapterServiceObj);
-  env->DeleteGlobalRef(android_bluetooth_UidTraffic.clazz);
-  android_bluetooth_UidTraffic.clazz = NULL;
+  if (sJniCallbacksObj) {
+    env->DeleteGlobalRef(sJniCallbacksObj);
+    sJniCallbacksObj = NULL;
+  }
+
+  if (sJniAdapterServiceObj) {
+    env->DeleteGlobalRef(sJniAdapterServiceObj);
+    sJniAdapterServiceObj = NULL;
+  }
+
+  if (android_bluetooth_UidTraffic.clazz) {
+    env->DeleteGlobalRef(android_bluetooth_UidTraffic.clazz);
+    android_bluetooth_UidTraffic.clazz = NULL;
+  }
+  {
+    std::lock_guard<std::mutex> lock(sSocketManagerMutex);
+    sSocketManager = nullptr;
+  }
   return JNI_TRUE;
 }
 
@@ -1068,79 +1122,21 @@
   return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
-static int connectSocketNative(JNIEnv* env, jobject object, jbyteArray address,
-                               jint type, jbyteArray uuidObj, jint channel,
-                               jint flag, jint callingUid) {
-  if (!sBluetoothSocketInterface) return -1;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    ALOGE("failed to get Bluetooth device address");
-    return -1;
+static jobject getSocketManagerNative(JNIEnv* env) {
+  std::lock_guard<std::mutex> lock(sSocketManagerMutex);
+  if (!sSocketManager.get()) {
+    sSocketManager =
+        new BluetoothSocketManagerBinderServer(sBluetoothSocketInterface);
   }
-
-  jbyte* uuid = NULL;
-  if (uuidObj != NULL) {
-    uuid = env->GetByteArrayElements(uuidObj, NULL);
-    if (!uuid) {
-      ALOGE("failed to get uuid");
-      env->ReleaseByteArrayElements(address, addr, 0);
-      return -1;
-    }
-  }
-
-  int socket_fd = -1;
-  bt_status_t status = sBluetoothSocketInterface->connect(
-      (RawAddress*)addr, (btsock_type_t)type, (const uint8_t*)uuid, channel,
-      &socket_fd, flag, callingUid);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Socket connection failed: %d", status);
-    socket_fd = -1;
-  } else if (socket_fd < 0) {
-    ALOGE("Fail to create file descriptor on socket fd");
-  }
-
-  env->ReleaseByteArrayElements(address, addr, 0);
-  env->ReleaseByteArrayElements(uuidObj, uuid, 0);
-  return socket_fd;
+  return javaObjectForIBinder(env, IInterface::asBinder(sSocketManager));
 }
 
-static int createSocketChannelNative(JNIEnv* env, jobject object, jint type,
-                                     jstring name_str, jbyteArray uuidObj,
-                                     jint channel, jint flag, jint callingUid) {
-  if (!sBluetoothSocketInterface) return -1;
+static void setSystemUiUidNative(JNIEnv* env, jobject obj, jint uid) {
+  android::bluetooth::systemUiUid = uid;
+}
 
-  ALOGV("%s: SOCK FLAG = %x", __func__, flag);
-
-  const char* service_name = NULL;
-  if (name_str != NULL) {
-    service_name = env->GetStringUTFChars(name_str, NULL);
-  }
-
-  jbyte* uuid = NULL;
-  if (uuidObj != NULL) {
-    uuid = env->GetByteArrayElements(uuidObj, NULL);
-    if (!uuid) {
-      ALOGE("failed to get uuid");
-      if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
-      return -1;
-    }
-  }
-
-  int socket_fd = -1;
-  bt_status_t status = sBluetoothSocketInterface->listen(
-      (btsock_type_t)type, service_name, (const uint8_t*)uuid, channel,
-      &socket_fd, flag, callingUid);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Socket listen failed: %d", status);
-    socket_fd = -1;
-  } else if (socket_fd < 0) {
-    ALOGE("Fail to creat file descriptor on socket fd");
-  }
-
-  if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
-  if (uuid) env->ReleaseByteArrayElements(uuidObj, uuid, 0);
-  return socket_fd;
+static void setForegroundUserIdNative(JNIEnv* env, jclass clazz, jint id) {
+  android::bluetooth::foregroundUserId = id;
 }
 
 static int readEnergyInfo() {
@@ -1180,6 +1176,19 @@
   delete[] argObjs;
 }
 
+static jbyteArray dumpMetricsNative(JNIEnv* env, jobject obj) {
+  ALOGI("%s", __func__);
+  if (!sBluetoothInterface) return env->NewByteArray(0);
+
+  std::string output;
+  sBluetoothInterface->dumpMetrics(&output);
+  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 jboolean factoryResetNative(JNIEnv* env, jobject obj) {
   ALOGV("%s", __func__);
   if (!sBluetoothInterface) return JNI_FALSE;
@@ -1231,13 +1240,15 @@
     {"pinReplyNative", "([BZI[B)Z", (void*)pinReplyNative},
     {"sspReplyNative", "([BIZI)Z", (void*)sspReplyNative},
     {"getRemoteServicesNative", "([B)Z", (void*)getRemoteServicesNative},
-    {"connectSocketNative", "([BI[BIII)I", (void*)connectSocketNative},
-    {"createSocketChannelNative", "(ILjava/lang/String;[BIII)I",
-     (void*)createSocketChannelNative},
+    {"getSocketManagerNative", "()Landroid/os/IBinder;",
+     (void*)getSocketManagerNative},
+    {"setSystemUiUidNative", "(I)V", (void*)setSystemUiUidNative},
+    {"setForegroundUserIdNative", "(I)V", (void*)setForegroundUserIdNative},
     {"alarmFiredNative", "()V", (void*)alarmFiredNative},
     {"readEnergyInfo", "()I", (void*)readEnergyInfo},
     {"dumpNative", "(Ljava/io/FileDescriptor;[Ljava/lang/String;)V",
      (void*)dumpNative},
+    {"dumpMetricsNative", "()[B", (void*)dumpMetricsNative},
     {"factoryResetNative", "()Z", (void*)factoryResetNative},
     {"interopDatabaseClearNative", "()V", (void*)interopDatabaseClearNative},
     {"interopDatabaseAddNative", "(I[BI)V", (void*)interopDatabaseAddNative}};
@@ -1301,19 +1312,24 @@
     return JNI_ERR;
   }
 
+  status = android::register_com_android_bluetooth_avrcp_target(e);
+  if (status < 0) {
+    ALOGE("jni new avrcp target registration failure: %d", status);
+  }
+
   status = android::register_com_android_bluetooth_avrcp_controller(e);
   if (status < 0) {
     ALOGE("jni avrcp controller registration failure: %d", status);
     return JNI_ERR;
   }
 
-  status = android::register_com_android_bluetooth_hid(e);
+  status = android::register_com_android_bluetooth_hid_host(e);
   if (status < 0) {
     ALOGE("jni hid registration failure: %d", status);
     return JNI_ERR;
   }
 
-  status = android::register_com_android_bluetooth_hidd(e);
+  status = android::register_com_android_bluetooth_hid_device(e);
   if (status < 0) {
     ALOGE("jni hidd registration failure: %d", status);
     return JNI_ERR;
@@ -1343,5 +1359,11 @@
     return JNI_ERR;
   }
 
+  status = android::register_com_android_bluetooth_hearing_aid(e);
+  if (status < 0) {
+    ALOGE("jni hearing aid registration failure: %d", status);
+    return JNI_ERR;
+  }
+
   return JNI_VERSION_1_6;
 }
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index e57a1d0..86e667b 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -25,6 +25,7 @@
 
 #include <base/bind.h>
 #include <string.h>
+#include <array>
 #include <memory>
 
 #include <cutils/log.h>
@@ -38,34 +39,38 @@
 #define asrt(s) \
   if (!(s)) ALOGE("%s(L%d): ASSERT %s failed! ##", __func__, __LINE__, #s)
 
-#define BD_ADDR_LEN 6
+using bluetooth::Uuid;
 
 #define UUID_PARAMS(uuid) uuid_lsb(uuid), uuid_msb(uuid)
 
-static void set_uuid(uint8_t* uuid, jlong uuid_msb, jlong uuid_lsb) {
-  for (int i = 0; i != 8; ++i) {
-    uuid[i] = (uuid_lsb >> (8 * i)) & 0xFF;
-    uuid[i + 8] = (uuid_msb >> (8 * i)) & 0xFF;
+static Uuid from_java_uuid(jlong uuid_msb, jlong uuid_lsb) {
+  std::array<uint8_t, Uuid::kNumBytes128> uu;
+  for (int i = 0; i < 8; i++) {
+    uu[7 - i] = (uuid_msb >> (8 * i)) & 0xFF;
+    uu[15 - i] = (uuid_lsb >> (8 * i)) & 0xFF;
   }
+  return Uuid::From128BitBE(uu);
 }
 
-static uint64_t uuid_lsb(const bt_uuid_t& uuid) {
+static uint64_t uuid_lsb(const Uuid& uuid) {
   uint64_t lsb = 0;
 
-  for (int i = 7; i >= 0; i--) {
+  auto uu = uuid.To128BitBE();
+  for (int i = 8; i <= 15; i++) {
     lsb <<= 8;
-    lsb |= uuid.uu[i];
+    lsb |= uu[i];
   }
 
   return lsb;
 }
 
-static uint64_t uuid_msb(const bt_uuid_t& uuid) {
+static uint64_t uuid_msb(const Uuid& uuid) {
   uint64_t msb = 0;
 
-  for (int i = 15; i >= 8; i--) {
+  auto uu = uuid.To128BitBE();
+  for (int i = 0; i <= 7; i++) {
     msb <<= 8;
-    msb |= uuid.uu[i];
+    msb |= uu[i];
   }
 
   return msb;
@@ -129,7 +134,7 @@
 static jmethodID method_onBatchScanReports;
 static jmethodID method_onBatchScanThresholdCrossed;
 
-static jmethodID method_CreateonTrackAdvFoundLostObject;
+static jmethodID method_createOnTrackAdvFoundLostObject;
 static jmethodID method_onTrackAdvFoundLost;
 static jmethodID method_onScanParamSetupCompleted;
 static jmethodID method_getSampleGattDbElement;
@@ -192,8 +197,7 @@
  * BTA client callbacks
  */
 
-void btgattc_register_app_cb(int status, int clientIf,
-                             const bt_uuid_t& app_uuid) {
+void btgattc_register_app_cb(int status, int clientIf, const Uuid& app_uuid) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
@@ -411,7 +415,7 @@
   ScopedLocalRef<jobject> trackadv_obj(
       sCallbackEnv.get(),
       sCallbackEnv->CallObjectMethod(
-          mCallbacksObj, method_CreateonTrackAdvFoundLostObject,
+          mCallbacksObj, method_createOnTrackAdvFoundLostObject,
           p_adv_track_info->client_if, p_adv_track_info->adv_pkt_len,
           jb_adv_pkt.get(), p_adv_track_info->scan_rsp_len, jb_scan_rsp.get(),
           p_adv_track_info->filt_index, p_adv_track_info->advertiser_state,
@@ -556,7 +560,7 @@
  * BTA server callbacks
  */
 
-void btgatts_register_app_cb(int status, int server_if, const bt_uuid_t& uuid) {
+void btgatts_register_app_cb(int status, int server_if, const Uuid& uuid) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerRegistered, status,
@@ -811,8 +815,8 @@
       env->GetMethodID(clazz, "onBatchScanReports", "(IIII[B)V");
   method_onBatchScanThresholdCrossed =
       env->GetMethodID(clazz, "onBatchScanThresholdCrossed", "(I)V");
-  method_CreateonTrackAdvFoundLostObject =
-      env->GetMethodID(clazz, "CreateonTrackAdvFoundLostObject",
+  method_createOnTrackAdvFoundLostObject =
+      env->GetMethodID(clazz, "createOnTrackAdvFoundLostObject",
                        "(II[BI[BIIILjava/lang/String;IIII)Lcom/android/"
                        "bluetooth/gatt/AdvtFilterOnFoundOnLostInfo;");
   method_onTrackAdvFoundLost = env->GetMethodID(
@@ -821,7 +825,7 @@
   method_onScanParamSetupCompleted =
       env->GetMethodID(clazz, "onScanParamSetupCompleted", "(II)V");
   method_getSampleGattDbElement =
-      env->GetMethodID(clazz, "GetSampleGattDbElement",
+      env->GetMethodID(clazz, "getSampleGattDbElement",
                        "()Lcom/android/bluetooth/gatt/GattDbElement;");
   method_onGetGattDb =
       env->GetMethodID(clazz, "onGetGattDb", "(ILjava/util/ArrayList;)V");
@@ -939,10 +943,8 @@
 static void gattClientRegisterAppNative(JNIEnv* env, jobject object,
                                         jlong app_uuid_lsb,
                                         jlong app_uuid_msb) {
-  bt_uuid_t uuid;
-
   if (!sGattIf) return;
-  set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
+  Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb);
   sGattIf->client->register_client(uuid);
 }
 
@@ -952,7 +954,7 @@
   sGattIf->client->unregister_client(clientIf);
 }
 
-void btgattc_register_scanner_cb(bt_uuid_t app_uuid, uint8_t scannerId,
+void btgattc_register_scanner_cb(const Uuid& app_uuid, uint8_t scannerId,
                                  uint8_t status) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -964,8 +966,7 @@
                                   jlong app_uuid_lsb, jlong app_uuid_msb) {
   if (!sGattIf) return;
 
-  bt_uuid_t uuid;
-  set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
+  Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb);
   sGattIf->scanner->RegisterScanner(
       base::Bind(&btgattc_register_scanner_cb, uuid));
 }
@@ -1041,8 +1042,7 @@
                                           jlong service_uuid_msb) {
   if (!sGattIf) return;
 
-  bt_uuid_t uuid;
-  set_uuid(uuid.uu, service_uuid_msb, service_uuid_lsb);
+  Uuid uuid = from_java_uuid(service_uuid_msb, service_uuid_lsb);
   sGattIf->client->search_service(conn_id, search_all ? 0 : &uuid);
 }
 
@@ -1052,8 +1052,7 @@
                                                   jlong service_uuid_msb) {
   if (!sGattIf) return;
 
-  bt_uuid_t uuid;
-  set_uuid(uuid.uu, service_uuid_msb, service_uuid_lsb);
+  Uuid uuid = from_java_uuid(service_uuid_msb, service_uuid_lsb);
   sGattIf->client->btif_gattc_discover_service_by_uuid(conn_id, uuid);
 }
 
@@ -1077,8 +1076,7 @@
     jint s_handle, jint e_handle, jint authReq) {
   if (!sGattIf) return;
 
-  bt_uuid_t uuid;
-  set_uuid(uuid.uu, uuid_msb, uuid_lsb);
+  Uuid uuid = from_java_uuid(uuid_msb, uuid_lsb);
   sGattIf->client->read_using_characteristic_uuid(conn_id, uuid, s_handle,
                                                   e_handle, authReq);
 }
@@ -1262,120 +1260,115 @@
                                status, client_if, filt_type, avbl_space);
 }
 
-static void gattClientScanFilterAddRemoveNative(
-    JNIEnv* env, jobject object, jint client_if, jint action, jint filt_type,
-    jint filt_index, jint company_id, jint company_id_mask, jlong uuid_lsb,
-    jlong uuid_msb, jlong uuid_mask_lsb, jlong uuid_mask_msb, jstring name,
-    jstring address, jbyte addr_type, jbyteArray data, jbyteArray mask) {
-  switch (filt_type) {
-    case 0:  // BTM_BLE_PF_ADDR_FILTER
-    {
-      RawAddress bda = str2addr(env, address);
-      sGattIf->scanner->ScanFilterAddRemove(
-          action, filt_type, filt_index, 0, 0, NULL, NULL, &bda, addr_type, {},
-          {}, base::Bind(&scan_filter_cfg_cb, client_if));
-      break;
-    }
+static void gattClientScanFilterAddNative(JNIEnv* env, jobject object,
+                                          jint client_if, jobjectArray filters,
+                                          jint filter_index) {
+  if (!sGattIf) return;
 
-    case 1:  // BTM_BLE_PF_SRVC_DATA
-    {
-      jbyte* data_array = env->GetByteArrayElements(data, 0);
-      int data_len = env->GetArrayLength(data);
-      std::vector<uint8_t> vec_data(data_array, data_array + data_len);
-      env->ReleaseByteArrayElements(data, data_array, JNI_ABORT);
+  jclass uuidClazz = env->FindClass("java/util/UUID");
+  jmethodID uuidGetMsb =
+      env->GetMethodID(uuidClazz, "getMostSignificantBits", "()J");
+  jmethodID uuidGetLsb =
+      env->GetMethodID(uuidClazz, "getLeastSignificantBits", "()J");
 
-      jbyte* mask_array = env->GetByteArrayElements(mask, NULL);
-      uint16_t mask_len = (uint16_t)env->GetArrayLength(mask);
-      std::vector<uint8_t> vec_mask(mask_array, mask_array + mask_len);
-      env->ReleaseByteArrayElements(mask, mask_array, JNI_ABORT);
+  std::vector<ApcfCommand> native_filters;
 
-      sGattIf->scanner->ScanFilterAddRemove(
-          action, filt_type, filt_index, 0, 0, NULL, NULL, NULL, 0,
-          std::move(vec_data), std::move(vec_mask),
-          base::Bind(&scan_filter_cfg_cb, client_if));
-      break;
-    }
-
-    case 2:  // BTM_BLE_PF_SRVC_UUID
-    case 3:  // BTM_BLE_PF_SRVC_SOL_UUID
-    {
-      bt_uuid_t uuid, uuid_mask;
-      set_uuid(uuid.uu, uuid_msb, uuid_lsb);
-      set_uuid(uuid_mask.uu, uuid_mask_msb, uuid_mask_lsb);
-      if (uuid_mask_lsb != 0 && uuid_mask_msb != 0)
-        sGattIf->scanner->ScanFilterAddRemove(
-            action, filt_type, filt_index, 0, 0, &uuid, &uuid_mask, NULL, 0, {},
-            {}, base::Bind(&scan_filter_cfg_cb, client_if));
-      else
-        sGattIf->scanner->ScanFilterAddRemove(
-            action, filt_type, filt_index, 0, 0, &uuid, NULL, NULL, 0, {}, {},
-            base::Bind(&scan_filter_cfg_cb, client_if));
-      break;
-    }
-
-    case 4:  // BTM_BLE_PF_LOCAL_NAME
-    {
-      const char* c_name = env->GetStringUTFChars(name, NULL);
-      if (c_name != NULL && strlen(c_name) != 0) {
-        std::vector<uint8_t> vec_name(c_name, c_name + strlen(c_name));
-        env->ReleaseStringUTFChars(name, c_name);
-        sGattIf->scanner->ScanFilterAddRemove(
-            action, filt_type, filt_index, 0, 0, NULL, NULL, NULL, 0,
-            std::move(vec_name), {},
-            base::Bind(&scan_filter_cfg_cb, client_if));
-      }
-      break;
-    }
-
-    case 5:  // BTM_BLE_PF_MANU_DATA
-    case 6:  // BTM_BLE_PF_SRVC_DATA_PATTERN
-    {
-      jbyte* data_array = env->GetByteArrayElements(data, 0);
-      int data_len = env->GetArrayLength(data);
-      std::vector<uint8_t> vec_data(data_array, data_array + data_len);
-      env->ReleaseByteArrayElements(data, data_array, JNI_ABORT);
-
-      jbyte* mask_array = env->GetByteArrayElements(mask, NULL);
-      uint16_t mask_len = (uint16_t)env->GetArrayLength(mask);
-      std::vector<uint8_t> vec_mask(mask_array, mask_array + mask_len);
-      env->ReleaseByteArrayElements(mask, mask_array, JNI_ABORT);
-
-      sGattIf->scanner->ScanFilterAddRemove(
-          action, filt_type, filt_index, company_id, company_id_mask, NULL,
-          NULL, NULL, 0, std::move(vec_data), std::move(vec_mask),
-          base::Bind(&scan_filter_cfg_cb, client_if));
-      break;
-    }
-
-    default:
-      break;
+  int numFilters = env->GetArrayLength(filters);
+  if (numFilters == 0) {
+    sGattIf->scanner->ScanFilterAdd(filter_index, std::move(native_filters),
+                                    base::Bind(&scan_filter_cfg_cb, client_if));
+    return;
   }
-}
 
-static void gattClientScanFilterAddNative(
-    JNIEnv* env, jobject object, jint client_if, jint filt_type,
-    jint filt_index, jint company_id, jint company_id_mask, jlong uuid_lsb,
-    jlong uuid_msb, jlong uuid_mask_lsb, jlong uuid_mask_msb, jstring name,
-    jstring address, jbyte addr_type, jbyteArray data, jbyteArray mask) {
-  if (!sGattIf) return;
-  int action = 0;
-  gattClientScanFilterAddRemoveNative(
-      env, object, client_if, action, filt_type, filt_index, company_id,
-      company_id_mask, uuid_lsb, uuid_msb, uuid_mask_lsb, uuid_mask_msb, name,
-      address, addr_type, data, mask);
-}
+  jclass entryClazz =
+      env->GetObjectClass(env->GetObjectArrayElement(filters, 0));
 
-static void gattClientScanFilterDeleteNative(
-    JNIEnv* env, jobject object, jint client_if, jint filt_type,
-    jint filt_index, jint company_id, jint company_id_mask, jlong uuid_lsb,
-    jlong uuid_msb, jlong uuid_mask_lsb, jlong uuid_mask_msb, jstring name,
-    jstring address, jbyte addr_type, jbyteArray data, jbyteArray mask) {
-  if (!sGattIf) return;
-  int action = 1;
-  gattClientScanFilterAddRemoveNative(
-      env, object, client_if, action, filt_type, filt_index, company_id,
-      company_id_mask, uuid_lsb, uuid_msb, uuid_mask_lsb, uuid_mask_msb, name,
-      address, addr_type, data, mask);
+  jfieldID typeFid = env->GetFieldID(entryClazz, "type", "B");
+  jfieldID addressFid =
+      env->GetFieldID(entryClazz, "address", "Ljava/lang/String;");
+  jfieldID addrTypeFid = env->GetFieldID(entryClazz, "addr_type", "B");
+  jfieldID uuidFid = env->GetFieldID(entryClazz, "uuid", "Ljava/util/UUID;");
+  jfieldID uuidMaskFid =
+      env->GetFieldID(entryClazz, "uuid_mask", "Ljava/util/UUID;");
+  jfieldID nameFid = env->GetFieldID(entryClazz, "name", "Ljava/lang/String;");
+  jfieldID companyFid = env->GetFieldID(entryClazz, "company", "I");
+  jfieldID companyMaskFid = env->GetFieldID(entryClazz, "company_mask", "I");
+  jfieldID dataFid = env->GetFieldID(entryClazz, "data", "[B");
+  jfieldID dataMaskFid = env->GetFieldID(entryClazz, "data_mask", "[B");
+
+  for (int i = 0; i < numFilters; ++i) {
+    ApcfCommand curr;
+
+    ScopedLocalRef<jobject> current(env,
+                                    env->GetObjectArrayElement(filters, i));
+
+    curr.type = env->GetByteField(current.get(), typeFid);
+
+    ScopedLocalRef<jstring> address(
+        env, (jstring)env->GetObjectField(current.get(), addressFid));
+    if (address.get() != NULL) {
+      curr.address = str2addr(env, address.get());
+    }
+
+    curr.addr_type = env->GetByteField(current.get(), addrTypeFid);
+
+    ScopedLocalRef<jobject> uuid(env,
+                                 env->GetObjectField(current.get(), uuidFid));
+    if (uuid.get() != NULL) {
+      jlong uuid_msb = env->CallLongMethod(uuid.get(), uuidGetMsb);
+      jlong uuid_lsb = env->CallLongMethod(uuid.get(), uuidGetLsb);
+      curr.uuid = from_java_uuid(uuid_msb, uuid_lsb);
+    }
+
+    ScopedLocalRef<jobject> uuid_mask(
+        env, env->GetObjectField(current.get(), uuidMaskFid));
+    if (uuid.get() != NULL) {
+      jlong uuid_msb = env->CallLongMethod(uuid_mask.get(), uuidGetMsb);
+      jlong uuid_lsb = env->CallLongMethod(uuid_mask.get(), uuidGetLsb);
+      curr.uuid_mask = from_java_uuid(uuid_msb, uuid_lsb);
+    }
+
+    ScopedLocalRef<jstring> name(
+        env, (jstring)env->GetObjectField(current.get(), nameFid));
+    if (name.get() != NULL) {
+      const char* c_name = env->GetStringUTFChars(name.get(), NULL);
+      if (c_name != NULL && strlen(c_name) != 0) {
+        curr.name = std::vector<uint8_t>(c_name, c_name + strlen(c_name));
+        env->ReleaseStringUTFChars(name.get(), c_name);
+      }
+    }
+
+    curr.company = env->GetIntField(current.get(), companyFid);
+
+    curr.company_mask = env->GetIntField(current.get(), companyMaskFid);
+
+    ScopedLocalRef<jbyteArray> data(
+        env, (jbyteArray)env->GetObjectField(current.get(), dataFid));
+    if (data.get() != NULL) {
+      jbyte* data_array = env->GetByteArrayElements(data.get(), 0);
+      int data_len = env->GetArrayLength(data.get());
+      if (data_array && data_len) {
+        curr.data = std::vector<uint8_t>(data_array, data_array + data_len);
+        env->ReleaseByteArrayElements(data.get(), data_array, JNI_ABORT);
+      }
+    }
+
+    ScopedLocalRef<jbyteArray> data_mask(
+        env, (jbyteArray)env->GetObjectField(current.get(), dataMaskFid));
+    if (data_mask.get() != NULL) {
+      jbyte* data_array = env->GetByteArrayElements(data_mask.get(), 0);
+      int data_len = env->GetArrayLength(data_mask.get());
+      if (data_array && data_len) {
+        curr.data_mask =
+            std::vector<uint8_t>(data_array, data_array + data_len);
+        env->ReleaseByteArrayElements(data_mask.get(), data_array, JNI_ABORT);
+      }
+    }
+    native_filters.push_back(curr);
+  }
+
+  sGattIf->scanner->ScanFilterAdd(filter_index, std::move(native_filters),
+                                  base::Bind(&scan_filter_cfg_cb, client_if));
 }
 
 static void gattClientScanFilterClearNative(JNIEnv* env, jobject object,
@@ -1409,10 +1402,12 @@
                                                 jint client_if, jstring address,
                                                 jint min_interval,
                                                 jint max_interval, jint latency,
-                                                jint timeout) {
+                                                jint timeout, jint min_ce_len,
+                                                jint max_ce_len) {
   if (!sGattIf) return;
-  sGattIf->client->conn_parameter_update(str2addr(env, address), min_interval,
-                                         max_interval, latency, timeout);
+  sGattIf->client->conn_parameter_update(
+      str2addr(env, address), min_interval, max_interval, latency, timeout,
+      (uint16_t)min_ce_len, (uint16_t)max_ce_len);
 }
 
 void batchscan_cfg_storage_cb(uint8_t client_if, uint8_t status) {
@@ -1469,9 +1464,8 @@
 static void gattServerRegisterAppNative(JNIEnv* env, jobject object,
                                         jlong app_uuid_lsb,
                                         jlong app_uuid_msb) {
-  bt_uuid_t uuid;
   if (!sGattIf) return;
-  set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
+  Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb);
   sGattIf->server->register_server(uuid);
 }
 
@@ -1563,10 +1557,11 @@
 
     fid = env->GetFieldID(gattDbElementClazz, "uuid", "Ljava/util/UUID;");
     ScopedLocalRef<jobject> uuid(env, env->GetObjectField(element.get(), fid));
-
-    jlong uuid_msb = env->CallLongMethod(uuid.get(), uuidGetMsb);
-    jlong uuid_lsb = env->CallLongMethod(uuid.get(), uuidGetLsb);
-    set_uuid(curr.uuid.uu, uuid_msb, uuid_lsb);
+    if (uuid.get() != NULL) {
+      jlong uuid_msb = env->CallLongMethod(uuid.get(), uuidGetMsb);
+      jlong uuid_lsb = env->CallLongMethod(uuid.get(), uuidGetLsb);
+      curr.uuid = from_java_uuid(uuid_msb, uuid_lsb);
+    }
 
     fid = env->GetFieldID(gattDbElementClazz, "type", "I");
     curr.type =
@@ -2051,8 +2046,7 @@
 
   RawAddress bt_bda1 = str2addr(env, bda1);
 
-  bt_uuid_t uuid1;
-  set_uuid(uuid1.uu, uuid1_msb, uuid1_lsb);
+  Uuid uuid1 = from_java_uuid(uuid1_msb, uuid1_lsb);
 
   btgatt_test_params_t params;
   params.bda1 = &bt_bda1;
@@ -2128,11 +2122,8 @@
     {"gattClientScanFilterParamClearAllNative", "(I)V",
      (void*)gattClientScanFilterParamClearAllNative},
     {"gattClientScanFilterAddNative",
-     "(IIIIIJJJJLjava/lang/String;Ljava/lang/String;B[B[B)V",
+     "(I[Lcom/android/bluetooth/gatt/ScanFilterQueue$Entry;I)V",
      (void*)gattClientScanFilterAddNative},
-    {"gattClientScanFilterDeleteNative",
-     "(IIIIIJJJJLjava/lang/String;Ljava/lang/String;B[B[B)V",
-     (void*)gattClientScanFilterDeleteNative},
     {"gattClientScanFilterClearNative", "(II)V",
      (void*)gattClientScanFilterClearNative},
     {"gattClientScanFilterEnableNative", "(IZ)V",
@@ -2185,7 +2176,7 @@
      (void*)gattClientReadRemoteRssiNative},
     {"gattClientConfigureMTUNative", "(II)V",
      (void*)gattClientConfigureMTUNative},
-    {"gattConnectionParameterUpdateNative", "(ILjava/lang/String;IIII)V",
+    {"gattConnectionParameterUpdateNative", "(ILjava/lang/String;IIIIII)V",
      (void*)gattConnectionParameterUpdateNative},
     {"gattServerRegisterAppNative", "(JJ)V",
      (void*)gattServerRegisterAppNative},
@@ -2229,4 +2220,4 @@
          jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattService",
                                   sMethods, NELEM(sMethods));
 }
-}
+}  // namespace android
diff --git a/jni/com_android_bluetooth_hearing_aid.cpp b/jni/com_android_bluetooth_hearing_aid.cpp
new file mode 100644
index 0000000..1602aac
--- /dev/null
+++ b/jni/com_android_bluetooth_hearing_aid.cpp
@@ -0,0 +1,256 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "BluetoothHearingAidServiceJni"
+
+#define LOG_NDEBUG 0
+
+#include "android_runtime/AndroidRuntime.h"
+#include "base/logging.h"
+#include "com_android_bluetooth.h"
+#include "hardware/bt_hearing_aid.h"
+
+#include <string.h>
+#include <shared_mutex>
+
+using bluetooth::hearing_aid::ConnectionState;
+using bluetooth::hearing_aid::HearingAidInterface;
+using bluetooth::hearing_aid::HearingAidCallbacks;
+
+namespace android {
+static jmethodID method_onConnectionStateChanged;
+static jmethodID method_onDeviceAvailable;
+
+static HearingAidInterface* sHearingAidInterface = nullptr;
+static std::shared_timed_mutex interface_mutex;
+
+static jobject mCallbacksObj = nullptr;
+static std::shared_timed_mutex callbacks_mutex;
+
+class HearingAidCallbacksImpl : public HearingAidCallbacks {
+ public:
+  ~HearingAidCallbacksImpl() = default;
+  void OnConnectionState(ConnectionState state,
+                         const RawAddress& bd_addr) override {
+    LOG(INFO) << __func__;
+
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+    ScopedLocalRef<jbyteArray> addr(
+        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+    if (!addr.get()) {
+      LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+      return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                     (jbyte*)&bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
+                                 (jint)state, addr.get());
+  }
+
+  void OnDeviceAvailable(uint8_t capabilities, uint64_t hi_sync_id,
+                         const RawAddress& bd_addr) override {
+    LOG(INFO) << __func__ << ": capabilities=" << +capabilities
+              << " hi_sync_id=" << hi_sync_id;
+
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+    ScopedLocalRef<jbyteArray> addr(
+        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+    if (!addr.get()) {
+      LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+      return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                     (jbyte*)&bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDeviceAvailable,
+                                 (jbyte)capabilities, (jlong)hi_sync_id,
+                                 addr.get());
+  }
+};
+
+static HearingAidCallbacksImpl sHearingAidCallbacks;
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+  method_onConnectionStateChanged =
+      env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V");
+
+  method_onDeviceAvailable =
+      env->GetMethodID(clazz, "onDeviceAvailable", "(BJ[B)V");
+
+  LOG(INFO) << __func__ << ": succeeds";
+}
+
+static void initNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+  const bt_interface_t* btInf = getBluetoothInterface();
+  if (btInf == nullptr) {
+    LOG(ERROR) << "Bluetooth module is not loaded";
+    return;
+  }
+
+  if (sHearingAidInterface != nullptr) {
+    LOG(INFO) << "Cleaning up HearingAid Interface before initializing...";
+    sHearingAidInterface->Cleanup();
+    sHearingAidInterface = nullptr;
+  }
+
+  if (mCallbacksObj != nullptr) {
+    LOG(INFO) << "Cleaning up HearingAid callback object";
+    env->DeleteGlobalRef(mCallbacksObj);
+    mCallbacksObj = nullptr;
+  }
+
+  if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) {
+    LOG(ERROR) << "Failed to allocate Global Ref for Hearing Aid Callbacks";
+    return;
+  }
+
+  sHearingAidInterface = (HearingAidInterface*)btInf->get_profile_interface(
+      BT_PROFILE_HEARING_AID_ID);
+  if (sHearingAidInterface == nullptr) {
+    LOG(ERROR) << "Failed to get Bluetooth Hearing Aid Interface";
+    return;
+  }
+
+  sHearingAidInterface->Init(&sHearingAidCallbacks);
+}
+
+static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+  const bt_interface_t* btInf = getBluetoothInterface();
+  if (btInf == nullptr) {
+    LOG(ERROR) << "Bluetooth module is not loaded";
+    return;
+  }
+
+  if (sHearingAidInterface != nullptr) {
+    sHearingAidInterface->Cleanup();
+    sHearingAidInterface = nullptr;
+  }
+
+  if (mCallbacksObj != nullptr) {
+    env->DeleteGlobalRef(mCallbacksObj);
+    mCallbacksObj = nullptr;
+  }
+}
+
+static jboolean connectHearingAidNative(JNIEnv* env, jobject object,
+                                        jbyteArray address) {
+  LOG(INFO) << __func__;
+  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->Connect(*tmpraw);
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return JNI_TRUE;
+}
+
+static jboolean disconnectHearingAidNative(JNIEnv* env, jobject object,
+                                           jbyteArray address) {
+  LOG(INFO) << __func__;
+  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->Disconnect(*tmpraw);
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return JNI_TRUE;
+}
+
+static jboolean addToWhiteListNative(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->AddToWhiteList(*tmpraw);
+  env->ReleaseByteArrayElements(address, addr, 0);
+  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__
+               << ": Failed to get the Bluetooth Hearing Aid Interface";
+    return;
+  }
+  sHearingAidInterface->SetVolume(volume);
+}
+
+static JNINativeMethod sMethods[] = {
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initNative", "()V", (void*)initNative},
+    {"cleanupNative", "()V", (void*)cleanupNative},
+    {"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},
+};
+
+int register_com_android_bluetooth_hearing_aid(JNIEnv* env) {
+  return jniRegisterNativeMethods(
+      env, "com/android/bluetooth/hearingaid/HearingAidNativeInterface",
+      sMethods, NELEM(sMethods));
+}
+}  // namespace android
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index 5bb0d6e..8547aa6 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -20,10 +20,11 @@
 
 #include "android_runtime/AndroidRuntime.h"
 #include "com_android_bluetooth.h"
+#include "hardware/bluetooth_headset_callbacks.h"
+#include "hardware/bluetooth_headset_interface.h"
 #include "hardware/bt_hf.h"
 #include "utils/Log.h"
 
-#include <string.h>
 #include <mutex>
 #include <shared_mutex>
 
@@ -48,328 +49,338 @@
 static jmethodID method_onKeyPressed;
 static jmethodID method_onAtBind;
 static jmethodID method_onAtBiev;
+static jmethodID method_onAtBia;
 
-static const bthf_interface_t* sBluetoothHfpInterface = NULL;
+static bluetooth::headset::Interface* sBluetoothHfpInterface = nullptr;
 static std::shared_timed_mutex interface_mutex;
 
-static jobject mCallbacksObj = NULL;
+static jobject mCallbacksObj = nullptr;
 static std::shared_timed_mutex callbacks_mutex;
 
 static jbyteArray marshall_bda(RawAddress* bd_addr) {
   CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid()) return NULL;
+  if (!sCallbackEnv.valid()) return nullptr;
 
   jbyteArray addr = sCallbackEnv->NewByteArray(sizeof(RawAddress));
   if (!addr) {
     ALOGE("Fail to new jbyteArray bd addr");
-    return NULL;
+    return nullptr;
   }
   sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(RawAddress),
                                    (jbyte*)bd_addr);
   return addr;
 }
 
-static void connection_state_callback(bthf_connection_state_t state,
-                                      RawAddress* bd_addr) {
-  ALOGI("%s", __func__);
-
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
-
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) return;
-
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
-                               (jint)state, addr.get());
-}
-
-static void audio_state_callback(bthf_audio_state_t state,
-                                 RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
-
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) return;
-
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged,
-                               (jint)state, addr.get());
-}
-
-static void voice_recognition_callback(bthf_vr_state_t state,
-                                       RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
-
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+class JniHeadsetCallbacks : bluetooth::headset::Callbacks {
+ public:
+  static bluetooth::headset::Callbacks* GetInstance() {
+    static bluetooth::headset::Callbacks* instance = new JniHeadsetCallbacks();
+    return instance;
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVrStateChanged,
-                               (jint)state, addr.get());
-}
+  void ConnectionStateCallback(
+      bluetooth::headset::bthf_connection_state_t state,
+      RawAddress* bd_addr) override {
+    ALOGI("%s %d for %s", __func__, state, bd_addr->ToString().c_str());
 
-static void answer_call_callback(RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) return;
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
+                                 (jint)state, addr.get());
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAnswerCall, addr.get());
-}
+  void AudioStateCallback(bluetooth::headset::bthf_audio_state_t state,
+                          RawAddress* bd_addr) override {
+    ALOGI("%s, %d for %s", __func__, state, bd_addr->ToString().c_str());
 
-static void hangup_call_callback(RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) return;
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged,
+                                 (jint)state, addr.get());
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onHangupCall, addr.get());
-}
+  void VoiceRecognitionCallback(bluetooth::headset::bthf_vr_state_t state,
+                                RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void volume_control_callback(bthf_volume_type_t type, int volume,
-                                    RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVrStateChanged,
+                                 (jint)state, addr.get());
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeChanged,
-                               (jint)type, (jint)volume, addr.get());
-}
+  void AnswerCallCallback(RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void dial_call_callback(char* number, RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAnswerCall,
+                                 addr.get());
   }
 
-  ScopedLocalRef<jstring> js_number(sCallbackEnv.get(),
-                                    sCallbackEnv->NewStringUTF(number));
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDialCall,
-                               js_number.get(), addr.get());
-}
+  void HangupCallCallback(RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void dtmf_cmd_callback(char dtmf, RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onHangupCall,
+                                 addr.get());
   }
 
-  // TBD dtmf has changed from int to char
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSendDtmf, dtmf,
-                               addr.get());
-}
+  void VolumeControlCallback(bluetooth::headset::bthf_volume_type_t type,
+                             int volume, RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void noice_reduction_callback(bthf_nrec_t nrec, RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
-  }
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNoiceReductionEnable,
-                               nrec == BTHF_NREC_START, addr.get());
-}
-
-static void wbs_callback(bthf_wbs_config_t wbs_config, RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
-
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (addr.get() == NULL) return;
-
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWBS, wbs_config,
-                               addr.get());
-}
-
-static void at_chld_callback(bthf_chld_type_t chld, RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
-
-  ScopedLocalRef<jbyteArray> addr(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeChanged,
+                                 (jint)type, (jint)volume, addr.get());
   }
 
-  sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtChld, chld,
-                               addr.get());
-}
+  void DialCallCallback(char* number, RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void at_cnum_callback(RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    ScopedLocalRef<jstring> js_number(sCallbackEnv.get(),
+                                      sCallbackEnv->NewStringUTF(number));
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDialCall,
+                                 js_number.get(), addr.get());
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCnum, addr.get());
-}
+  void DtmfCmdCallback(char dtmf, RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void at_cind_callback(RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    // TBD dtmf has changed from int to char
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSendDtmf, dtmf,
+                                 addr.get());
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCind, addr.get());
-}
+  void NoiseReductionCallback(bluetooth::headset::bthf_nrec_t nrec,
+                              RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void at_cops_callback(RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
-
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNoiceReductionEnable,
+                                 nrec == bluetooth::headset::BTHF_NREC_START,
+                                 addr.get());
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCops, addr.get());
-}
+  void WbsCallback(bluetooth::headset::bthf_wbs_config_t wbs_config,
+                   RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void at_clcc_callback(RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (addr.get() == nullptr) return;
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWBS, wbs_config,
+                                 addr.get());
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtClcc, addr.get());
-}
+  void AtChldCallback(bluetooth::headset::bthf_chld_type_t chld,
+                      RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void unknown_at_callback(char* at_string, RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(
+        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                     (jbyte*)bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtChld, chld,
+                                 addr.get());
   }
 
-  ScopedLocalRef<jstring> js_at_string(sCallbackEnv.get(),
-                                       sCallbackEnv->NewStringUTF(at_string));
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onUnknownAt,
-                               js_at_string.get(), addr.get());
-}
+  void AtCnumCallback(RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void key_pressed_callback(RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (!addr.get()) {
-    ALOGE("Fail to new jbyteArray bd addr for audio state");
-    return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCnum, addr.get());
   }
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onKeyPressed, addr.get());
-}
+  void AtCindCallback(RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-static void at_bind_callback(char* at_string, RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (addr.get() == NULL) return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCind, addr.get());
+  }
 
-  ScopedLocalRef<jstring> js_at_string(sCallbackEnv.get(),
-                                       sCallbackEnv->NewStringUTF(at_string));
+  void AtCopsCallback(RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBind,
-                               js_at_string.get(), addr.get());
-}
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-static void at_biev_callback(bthf_hf_ind_type_t ind_id, int ind_value,
-                             RawAddress* bd_addr) {
-  std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
-  CallbackEnv sCallbackEnv(__func__);
-  if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtCops, addr.get());
+  }
 
-  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
-  if (addr.get() == NULL) return;
+  void AtClccCallback(RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBiev, ind_id,
-                               (jint)ind_value, addr.get());
-}
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
 
-static bthf_callbacks_t sBluetoothHfpCallbacks = {
-    sizeof(sBluetoothHfpCallbacks),
-    connection_state_callback,
-    audio_state_callback,
-    voice_recognition_callback,
-    answer_call_callback,
-    hangup_call_callback,
-    volume_control_callback,
-    dial_call_callback,
-    dtmf_cmd_callback,
-    noice_reduction_callback,
-    wbs_callback,
-    at_chld_callback,
-    at_cnum_callback,
-    at_cind_callback,
-    at_cops_callback,
-    at_clcc_callback,
-    unknown_at_callback,
-    at_bind_callback,
-    at_biev_callback,
-    key_pressed_callback};
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtClcc, addr.get());
+  }
+
+  void UnknownAtCallback(char* at_string, RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
+
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
+
+    ScopedLocalRef<jstring> js_at_string(sCallbackEnv.get(),
+                                         sCallbackEnv->NewStringUTF(at_string));
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onUnknownAt,
+                                 js_at_string.get(), addr.get());
+  }
+
+  void KeyPressedCallback(RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
+
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (!addr.get()) {
+      ALOGE("Fail to new jbyteArray bd addr for audio state");
+      return;
+    }
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onKeyPressed,
+                                 addr.get());
+  }
+
+  void AtBindCallback(char* at_string, RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
+
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (addr.get() == nullptr) return;
+
+    ScopedLocalRef<jstring> js_at_string(sCallbackEnv.get(),
+                                         sCallbackEnv->NewStringUTF(at_string));
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBind,
+                                 js_at_string.get(), addr.get());
+  }
+
+  void AtBievCallback(bluetooth::headset::bthf_hf_ind_type_t ind_id,
+                      int ind_value, RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
+
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (addr.get() == nullptr) return;
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBiev, ind_id,
+                                 (jint)ind_value, addr.get());
+  }
+
+  void AtBiaCallback(bool service, bool roam, bool signal, bool battery,
+                     RawAddress* bd_addr) override {
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || !mCallbacksObj) return;
+
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+    if (addr.get() == nullptr) return;
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBia, service, roam,
+                                 signal, battery, addr.get());
+  }
+};
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
   method_onConnectionStateChanged =
@@ -399,45 +410,50 @@
   method_onAtBind =
       env->GetMethodID(clazz, "onATBind", "(Ljava/lang/String;[B)V");
   method_onAtBiev = env->GetMethodID(clazz, "onATBiev", "(II[B)V");
+  method_onAtBia = env->GetMethodID(clazz, "onAtBia", "(ZZZZ[B)V");
 
   ALOGI("%s: succeeds", __func__);
 }
 
 static void initializeNative(JNIEnv* env, jobject object, jint max_hf_clients,
-                             jboolean inband_ringing_support) {
+                             jboolean inband_ringing_enabled) {
   std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
   std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
 
   const bt_interface_t* btInf = getBluetoothInterface();
-  if (btInf == NULL) {
-    ALOGE("Bluetooth module is not loaded");
+  if (!btInf) {
+    ALOGE("%s: Bluetooth module is not loaded", __func__);
+    jniThrowIOException(env, EINVAL);
     return;
   }
 
-  if (sBluetoothHfpInterface != NULL) {
-    ALOGW("Cleaning up Bluetooth Handsfree Interface before initializing...");
-    sBluetoothHfpInterface->cleanup();
-    sBluetoothHfpInterface = NULL;
+  if (sBluetoothHfpInterface) {
+    ALOGI("%s: Cleaning up Bluetooth Handsfree Interface before initializing",
+          __func__);
+    sBluetoothHfpInterface->Cleanup();
+    sBluetoothHfpInterface = nullptr;
   }
 
-  if (mCallbacksObj != NULL) {
-    ALOGW("Cleaning up Bluetooth Handsfree callback object");
+  if (mCallbacksObj) {
+    ALOGI("%s: Cleaning up Bluetooth Handsfree callback object", __func__);
     env->DeleteGlobalRef(mCallbacksObj);
-    mCallbacksObj = NULL;
+    mCallbacksObj = nullptr;
   }
 
   sBluetoothHfpInterface =
-      (bthf_interface_t*)btInf->get_profile_interface(BT_PROFILE_HANDSFREE_ID);
-  if (sBluetoothHfpInterface == NULL) {
-    ALOGE("Failed to get Bluetooth Handsfree Interface");
-    return;
+      (bluetooth::headset::Interface*)btInf->get_profile_interface(
+          BT_PROFILE_HANDSFREE_ID);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: Failed to get Bluetooth Handsfree Interface", __func__);
+    jniThrowIOException(env, EINVAL);
   }
-
-  bt_status_t status = sBluetoothHfpInterface->init(
-      &sBluetoothHfpCallbacks, max_hf_clients, inband_ringing_support);
+  bt_status_t status =
+      sBluetoothHfpInterface->Init(JniHeadsetCallbacks::GetInstance(),
+                                   max_hf_clients, inband_ringing_enabled);
   if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed to initialize Bluetooth HFP, status: %d", status);
-    sBluetoothHfpInterface = NULL;
+    ALOGE("%s: Failed to initialize Bluetooth Handsfree Interface, status: %d",
+          __func__, status);
+    sBluetoothHfpInterface = nullptr;
     return;
   }
 
@@ -449,37 +465,39 @@
   std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
 
   const bt_interface_t* btInf = getBluetoothInterface();
-  if (btInf == NULL) {
-    ALOGE("Bluetooth module is not loaded");
+  if (!btInf) {
+    ALOGW("%s: Bluetooth module is not loaded", __func__);
     return;
   }
 
-  if (sBluetoothHfpInterface != NULL) {
-    ALOGW("Cleaning up Bluetooth Handsfree Interface...");
-    sBluetoothHfpInterface->cleanup();
-    sBluetoothHfpInterface = NULL;
+  if (sBluetoothHfpInterface) {
+    ALOGI("%s: Cleaning up Bluetooth Handsfree Interface", __func__);
+    sBluetoothHfpInterface->Cleanup();
+    sBluetoothHfpInterface = nullptr;
   }
 
-  if (mCallbacksObj != NULL) {
-    ALOGW("Cleaning up Bluetooth Handsfree callback object");
+  if (mCallbacksObj) {
+    ALOGI("%s: Cleaning up Bluetooth Handsfree callback object", __func__);
     env->DeleteGlobalRef(mCallbacksObj);
-    mCallbacksObj = NULL;
+    mCallbacksObj = nullptr;
   }
 }
 
 static jboolean connectHfpNative(JNIEnv* env, jobject object,
                                  jbyteArray address) {
-  ALOGI("%s: sBluetoothHfpInterface: %p", __func__, sBluetoothHfpInterface);
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
-  bt_status_t status = sBluetoothHfpInterface->connect((RawAddress*)addr);
+  ALOGI("%s: device %s", __func__, ((RawAddress*)addr)->ToString().c_str());
+  bt_status_t status = sBluetoothHfpInterface->Connect((RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed HF connection, status: %d", status);
   }
@@ -490,15 +508,18 @@
 static jboolean disconnectHfpNative(JNIEnv* env, jobject object,
                                     jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
-  bt_status_t status = sBluetoothHfpInterface->disconnect((RawAddress*)addr);
+  ALOGI("%s: device %s", __func__, ((RawAddress*)addr)->ToString().c_str());
+  bt_status_t status = sBluetoothHfpInterface->Disconnect((RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed HF disconnection, status: %d", status);
   }
@@ -509,15 +530,18 @@
 static jboolean connectAudioNative(JNIEnv* env, jobject object,
                                    jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
-  bt_status_t status = sBluetoothHfpInterface->connect_audio((RawAddress*)addr);
+  ALOGI("%s: device %s", __func__, ((RawAddress*)addr)->ToString().c_str());
+  bt_status_t status = sBluetoothHfpInterface->ConnectAudio((RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed HF audio connection, status: %d", status);
   }
@@ -528,16 +552,19 @@
 static jboolean disconnectAudioNative(JNIEnv* env, jobject object,
                                       jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
+  ALOGI("%s: device %s", __func__, ((RawAddress*)addr)->ToString().c_str());
   bt_status_t status =
-      sBluetoothHfpInterface->disconnect_audio((RawAddress*)addr);
+      sBluetoothHfpInterface->DisconnectAudio((RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed HF audio disconnection, status: %d", status);
   }
@@ -548,16 +575,18 @@
 static jboolean startVoiceRecognitionNative(JNIEnv* env, jobject object,
                                             jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
   bt_status_t status =
-      sBluetoothHfpInterface->start_voice_recognition((RawAddress*)addr);
+      sBluetoothHfpInterface->StartVoiceRecognition((RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed to start voice recognition, status: %d", status);
   }
@@ -568,16 +597,18 @@
 static jboolean stopVoiceRecognitionNative(JNIEnv* env, jobject object,
                                            jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
   bt_status_t status =
-      sBluetoothHfpInterface->stop_voice_recognition((RawAddress*)addr);
+      sBluetoothHfpInterface->StopVoiceRecognition((RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed to stop voice recognition, status: %d", status);
   }
@@ -588,16 +619,19 @@
 static jboolean setVolumeNative(JNIEnv* env, jobject object, jint volume_type,
                                 jint volume, jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
-  bt_status_t status = sBluetoothHfpInterface->volume_control(
-      (bthf_volume_type_t)volume_type, volume, (RawAddress*)addr);
+  bt_status_t status = sBluetoothHfpInterface->VolumeControl(
+      (bluetooth::headset::bthf_volume_type_t)volume_type, volume,
+      (RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("FAILED to control volume, status: %d", status);
   }
@@ -607,13 +641,24 @@
 
 static jboolean notifyDeviceStatusNative(JNIEnv* env, jobject object,
                                          jint network_state, jint service_type,
-                                         jint signal, jint battery_charge) {
+                                         jint signal, jint battery_charge,
+                                         jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  bt_status_t status = sBluetoothHfpInterface->device_status_notification(
-      (bthf_network_state_t)network_state, (bthf_service_type_t)service_type,
-      signal, battery_charge);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+  if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+  bt_status_t status = sBluetoothHfpInterface->DeviceStatusNotification(
+      (bluetooth::headset::bthf_network_state_t)network_state,
+      (bluetooth::headset::bthf_service_type_t)service_type, signal,
+      battery_charge, (RawAddress*)addr);
+  env->ReleaseByteArrayElements(address, addr, 0);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("FAILED to notify device status, status: %d", status);
   }
@@ -623,18 +668,19 @@
 static jboolean copsResponseNative(JNIEnv* env, jobject object,
                                    jstring operator_str, jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
-  const char* operator_name = env->GetStringUTFChars(operator_str, NULL);
-
+  const char* operator_name = env->GetStringUTFChars(operator_str, nullptr);
   bt_status_t status =
-      sBluetoothHfpInterface->cops_response(operator_name, (RawAddress*)addr);
+      sBluetoothHfpInterface->CopsResponse(operator_name, (RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending cops response, status: %d", status);
   }
@@ -647,68 +693,45 @@
                                    jint num_active, jint num_held,
                                    jint call_state, jint signal, jint roam,
                                    jint battery_charge, jbyteArray address) {
-  ALOGI("%s: sBluetoothHfpInterface: %p", __func__, sBluetoothHfpInterface);
-
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
-  bt_status_t status = sBluetoothHfpInterface->cind_response(
-      service, num_active, num_held, (bthf_call_state_t)call_state, signal,
-      roam, battery_charge, (RawAddress*)addr);
+  bt_status_t status = sBluetoothHfpInterface->CindResponse(
+      service, num_active, num_held,
+      (bluetooth::headset::bthf_call_state_t)call_state, signal, roam,
+      battery_charge, (RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed cind_response, status: %d", status);
+    ALOGE("%s: failed, status: %d", __func__, status);
   }
   env->ReleaseByteArrayElements(address, addr, 0);
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
-static jboolean bindResponseNative(JNIEnv* env, jobject object, jint ind_id,
-                                   jboolean ind_status, jbyteArray address) {
-  ALOGI("%s: sBluetoothHfpInterface: %p", __func__, sBluetoothHfpInterface);
-
-  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  bt_status_t status = sBluetoothHfpInterface->bind_response(
-      (bthf_hf_ind_type_t)ind_id,
-      ind_status ? BTHF_HF_IND_ENABLED : BTHF_HF_IND_DISABLED,
-      (RawAddress*)addr);
-
-  if (status != BT_STATUS_SUCCESS)
-    ALOGE("%s: Failed bind_response, status: %d", __func__, status);
-
-  env->ReleaseByteArrayElements(address, addr, 0);
-  return (status == BT_STATUS_SUCCESS ? JNI_TRUE : JNI_FALSE);
-}
-
 static jboolean atResponseStringNative(JNIEnv* env, jobject object,
                                        jstring response_str,
                                        jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
-  const char* response = env->GetStringUTFChars(response_str, NULL);
-
-  bt_status_t status = sBluetoothHfpInterface->formatted_at_response(
-      response, (RawAddress*)addr);
+  const char* response = env->GetStringUTFChars(response_str, nullptr);
+  bt_status_t status =
+      sBluetoothHfpInterface->FormattedAtResponse(response, (RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed formatted AT response, status: %d", status);
   }
@@ -721,16 +744,19 @@
                                      jint response_code, jint cmee_code,
                                      jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
-  bt_status_t status = sBluetoothHfpInterface->at_response(
-      (bthf_at_response_t)response_code, cmee_code, (RawAddress*)addr);
+  bt_status_t status = sBluetoothHfpInterface->AtResponse(
+      (bluetooth::headset::bthf_at_response_t)response_code, cmee_code,
+      (RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed AT response, status: %d", status);
   }
@@ -743,81 +769,124 @@
                                    jboolean mpty, jstring number_str, jint type,
                                    jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
   if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
-  const char* number = NULL;
-  if (number_str) number = env->GetStringUTFChars(number_str, NULL);
-
-  bt_status_t status = sBluetoothHfpInterface->clcc_response(
-      index, (bthf_call_direction_t)dir, (bthf_call_state_t)callStatus,
-      (bthf_call_mode_t)mode,
-      mpty ? BTHF_CALL_MPTY_TYPE_MULTI : BTHF_CALL_MPTY_TYPE_SINGLE, number,
-      (bthf_call_addrtype_t)type, (RawAddress*)addr);
+  const char* number = nullptr;
+  if (number_str) {
+    number = env->GetStringUTFChars(number_str, nullptr);
+  }
+  bt_status_t status = sBluetoothHfpInterface->ClccResponse(
+      index, (bluetooth::headset::bthf_call_direction_t)dir,
+      (bluetooth::headset::bthf_call_state_t)callStatus,
+      (bluetooth::headset::bthf_call_mode_t)mode,
+      mpty ? bluetooth::headset::BTHF_CALL_MPTY_TYPE_MULTI
+           : bluetooth::headset::BTHF_CALL_MPTY_TYPE_SINGLE,
+      number, (bluetooth::headset::bthf_call_addrtype_t)type,
+      (RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed sending CLCC response, status: %d", status);
   }
   env->ReleaseByteArrayElements(address, addr, 0);
-  if (number) env->ReleaseStringUTFChars(number_str, number);
+  if (number) {
+    env->ReleaseStringUTFChars(number_str, number);
+  }
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
 static jboolean phoneStateChangeNative(JNIEnv* env, jobject object,
                                        jint num_active, jint num_held,
                                        jint call_state, jstring number_str,
-                                       jint type) {
+                                       jint type, jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  const char* number = env->GetStringUTFChars(number_str, NULL);
-
-  bt_status_t status = sBluetoothHfpInterface->phone_state_change(
-      num_active, num_held, (bthf_call_state_t)call_state, number,
-      (bthf_call_addrtype_t)type);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+  if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+  const char* number = env->GetStringUTFChars(number_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,
+      (RawAddress*)addr);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed report phone state change, status: %d", status);
   }
   env->ReleaseStringUTFChars(number_str, number);
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
-}
-
-static jboolean configureWBSNative(JNIEnv* env, jobject object,
-                                   jbyteArray address, jint codec_config) {
-  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  jbyte* addr = env->GetByteArrayElements(address, NULL);
-  if (!addr) {
-    jniThrowIOException(env, EINVAL);
-    return JNI_FALSE;
-  }
-
-  bt_status_t status = sBluetoothHfpInterface->configure_wbs(
-      (RawAddress*)addr, (bthf_wbs_config_t)codec_config);
-  if (status != BT_STATUS_SUCCESS) {
-    ALOGE("Failed HF WBS codec config, status: %d", status);
-  }
   env->ReleaseByteArrayElements(address, addr, 0);
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
 static jboolean setScoAllowedNative(JNIEnv* env, jobject object,
                                     jboolean value) {
-  if (!sBluetoothHfpInterface) return JNI_FALSE;
-
-  bt_status_t status =
-      sBluetoothHfpInterface->set_sco_allowed(value == JNI_TRUE);
+  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  bt_status_t status = sBluetoothHfpInterface->SetScoAllowed(value == JNI_TRUE);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed HF set sco allowed, status: %d", status);
   }
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
+static jboolean sendBsirNative(JNIEnv* env, jobject object, jboolean value,
+                               jbyteArray address) {
+  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+  bt_status_t status =
+      sBluetoothHfpInterface->SendBsir(value == JNI_TRUE, (RawAddress*)addr);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed sending BSIR, value=%d, status=%d", value, status);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean setActiveDeviceNative(JNIEnv* env, jobject object,
+                                      jbyteArray address) {
+  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+  bt_status_t status =
+      sBluetoothHfpInterface->SetActiveDevice((RawAddress*)addr);
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("Failed to set active device, status: %d", status);
+  }
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void*)classInitNative},
     {"initializeNative", "(IZ)V", (void*)initializeNative},
@@ -830,25 +899,25 @@
      (void*)startVoiceRecognitionNative},
     {"stopVoiceRecognitionNative", "([B)Z", (void*)stopVoiceRecognitionNative},
     {"setVolumeNative", "(II[B)Z", (void*)setVolumeNative},
-    {"notifyDeviceStatusNative", "(IIII)Z", (void*)notifyDeviceStatusNative},
+    {"notifyDeviceStatusNative", "(IIII[B)Z", (void*)notifyDeviceStatusNative},
     {"copsResponseNative", "(Ljava/lang/String;[B)Z",
      (void*)copsResponseNative},
     {"cindResponseNative", "(IIIIIII[B)Z", (void*)cindResponseNative},
-    {"bindResponseNative", "(IZ[B)Z", (void*)bindResponseNative},
     {"atResponseStringNative", "(Ljava/lang/String;[B)Z",
      (void*)atResponseStringNative},
     {"atResponseCodeNative", "(II[B)Z", (void*)atResponseCodeNative},
     {"clccResponseNative", "(IIIIZLjava/lang/String;I[B)Z",
      (void*)clccResponseNative},
-    {"phoneStateChangeNative", "(IIILjava/lang/String;I)Z",
+    {"phoneStateChangeNative", "(IIILjava/lang/String;I[B)Z",
      (void*)phoneStateChangeNative},
-    {"configureWBSNative", "([BI)Z", (void*)configureWBSNative},
     {"setScoAllowedNative", "(Z)Z", (void*)setScoAllowedNative},
+    {"sendBsirNative", "(Z[B)Z", (void*)sendBsirNative},
+    {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative},
 };
 
 int register_com_android_bluetooth_hfp(JNIEnv* env) {
   return jniRegisterNativeMethods(
-      env, "com/android/bluetooth/hfp/HeadsetStateMachine", sMethods,
+      env, "com/android/bluetooth/hfp/HeadsetNativeInterface", sMethods,
       NELEM(sMethods));
 }
 
diff --git a/jni/com_android_bluetooth_hfpclient.cpp b/jni/com_android_bluetooth_hfpclient.cpp
index 04e963b..3dfc86e 100644
--- a/jni/com_android_bluetooth_hfpclient.cpp
+++ b/jni/com_android_bluetooth_hfpclient.cpp
@@ -95,8 +95,12 @@
 static void vr_cmd_cb(const RawAddress* bd_addr, bthf_client_vr_state_t state) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
+
+  ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
+  if (!addr.get()) return;
+
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVrStateChanged,
-                               (jint)state);
+                               (jint)state, addr.get());
 }
 
 static void network_state_cb(const RawAddress* bd_addr,
@@ -348,7 +352,8 @@
       env->GetMethodID(clazz, "onConnectionStateChanged", "(III[B)V");
   method_onAudioStateChanged =
       env->GetMethodID(clazz, "onAudioStateChanged", "(I[B)V");
-  method_onVrStateChanged = env->GetMethodID(clazz, "onVrStateChanged", "(I)V");
+  method_onVrStateChanged =
+      env->GetMethodID(clazz, "onVrStateChanged", "(I[B)V");
   method_onNetworkState = env->GetMethodID(clazz, "onNetworkState", "(I[B)V");
   method_onNetworkRoaming = env->GetMethodID(clazz, "onNetworkRoaming", "(I[B)V");
   method_onNetworkSignal = env->GetMethodID(clazz, "onNetworkSignal", "(I[B)V");
@@ -576,17 +581,18 @@
     return JNI_FALSE;
   }
 
-  const char* number = NULL;
-  if (number_str != NULL) {
-    number = env->GetStringUTFChars(number_str, NULL);
+  const char* number = nullptr;
+  if (number_str != nullptr) {
+    number = env->GetStringUTFChars(number_str, nullptr);
   }
-
   bt_status_t status =
-      sBluetoothHfpClientInterface->dial((const RawAddress*)addr, number);
+    sBluetoothHfpClientInterface->dial((const RawAddress*)addr,
+                                       number == nullptr ? "" : number);
+
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed to dial, status: %d", status);
   }
-  if (number != NULL) {
+  if (number != nullptr) {
     env->ReleaseStringUTFChars(number_str, number);
   }
   env->ReleaseByteArrayElements(address, addr, 0);
@@ -747,7 +753,6 @@
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
-
   const char* arg = NULL;
   if (arg_str != NULL) {
     arg = env->GetStringUTFChars(arg_str, NULL);
diff --git a/jni/com_android_bluetooth_hidd.cpp b/jni/com_android_bluetooth_hid_device.cpp
similarity index 90%
rename from jni/com_android_bluetooth_hidd.cpp
rename to jni/com_android_bluetooth_hid_device.cpp
index b0e084a..403e904 100644
--- a/jni/com_android_bluetooth_hidd.cpp
+++ b/jni/com_android_bluetooth_hid_device.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "BluetoothHidDevServiceJni"
+#define LOG_TAG "BluetoothHidDeviceServiceJni"
 
 #define LOG_NDEBUG 0
 
@@ -32,7 +32,7 @@
 static jmethodID method_onGetReport;
 static jmethodID method_onSetReport;
 static jmethodID method_onSetProtocol;
-static jmethodID method_onIntrData;
+static jmethodID method_onInterruptData;
 static jmethodID method_onVirtualCableUnplug;
 
 static const bthd_interface_t* sHiddIf = NULL;
@@ -132,7 +132,7 @@
   }
   sCallbackEnv->SetByteArrayRegion(data.get(), 0, len, (jbyte*)p_data);
 
-  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onIntrData,
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onInterruptData,
                                (jbyte)report_id, data.get());
 }
 
@@ -163,7 +163,7 @@
   method_onGetReport = env->GetMethodID(clazz, "onGetReport", "(BBS)V");
   method_onSetReport = env->GetMethodID(clazz, "onSetReport", "(BB[B)V");
   method_onSetProtocol = env->GetMethodID(clazz, "onSetProtocol", "(B)V");
-  method_onIntrData = env->GetMethodID(clazz, "onIntrData", "(B[B)V");
+  method_onInterruptData = env->GetMethodID(clazz, "onInterruptData", "(B[B)V");
   method_onVirtualCableUnplug =
       env->GetMethodID(clazz, "onVirtualCableUnplug", "()V");
 }
@@ -261,6 +261,11 @@
                                   jintArray p_in_qos, jintArray p_out_qos) {
   ALOGV("%s enter", __FUNCTION__);
 
+  if (!sHiddIf) {
+    ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__);
+    return JNI_FALSE;
+  }
+
   jboolean result = JNI_FALSE;
   bthd_app_param_t app_param;
   bthd_qos_param_t in_qos;
@@ -309,6 +314,11 @@
 
   jboolean result = JNI_FALSE;
 
+  if (!sHiddIf) {
+    ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__);
+    return JNI_FALSE;
+  }
+
   bt_status_t ret = sHiddIf->unregister_app();
 
   ALOGV("%s: unregister_app() returned %d", __FUNCTION__, ret);
@@ -325,6 +335,12 @@
 static jboolean sendReportNative(JNIEnv* env, jobject thiz, jint id,
                                  jbyteArray data) {
   jboolean result = JNI_FALSE;
+
+  if (!sHiddIf) {
+    ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__);
+    return JNI_FALSE;
+  }
+
   jsize size;
   uint8_t* buf;
 
@@ -351,6 +367,11 @@
                                   jbyte id, jbyteArray data) {
   ALOGV("%s enter", __FUNCTION__);
 
+  if (!sHiddIf) {
+    ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__);
+    return JNI_FALSE;
+  }
+
   jboolean result = JNI_FALSE;
   jsize size;
   uint8_t* buf;
@@ -382,6 +403,11 @@
 static jboolean reportErrorNative(JNIEnv* env, jobject thiz, jbyte error) {
   ALOGV("%s enter", __FUNCTION__);
 
+  if (!sHiddIf) {
+    ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__);
+    return JNI_FALSE;
+  }
+
   jboolean result = JNI_FALSE;
 
   bt_status_t ret = sHiddIf->report_error(error);
@@ -400,6 +426,11 @@
 static jboolean unplugNative(JNIEnv* env, jobject thiz) {
   ALOGV("%s enter", __FUNCTION__);
 
+  if (!sHiddIf) {
+    ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__);
+    return JNI_FALSE;
+  }
+
   jboolean result = JNI_FALSE;
 
   bt_status_t ret = sHiddIf->virtual_cable_unplug();
@@ -418,6 +449,11 @@
 static jboolean connectNative(JNIEnv* env, jobject thiz, jbyteArray address) {
   ALOGV("%s enter", __FUNCTION__);
 
+  if (!sHiddIf) {
+    ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__);
+    return JNI_FALSE;
+  }
+
   jboolean result = JNI_FALSE;
 
   jbyte* addr = env->GetByteArrayElements(address, NULL);
@@ -442,6 +478,11 @@
 static jboolean disconnectNative(JNIEnv* env, jobject thiz) {
   ALOGV("%s enter", __FUNCTION__);
 
+  if (!sHiddIf) {
+    ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__);
+    return JNI_FALSE;
+  }
+
   jboolean result = JNI_FALSE;
 
   bt_status_t ret = sHiddIf->disconnect();
@@ -473,9 +514,9 @@
     {"disconnectNative", "()Z", (void*)disconnectNative},
 };
 
-int register_com_android_bluetooth_hidd(JNIEnv* env) {
-  return jniRegisterNativeMethods(env,
-                                  "com/android/bluetooth/hid/HidDevService",
-                                  sMethods, NELEM(sMethods));
+int register_com_android_bluetooth_hid_device(JNIEnv* env) {
+  return jniRegisterNativeMethods(
+      env, "com/android/bluetooth/hid/HidDeviceNativeInterface", sMethods,
+      NELEM(sMethods));
 }
-}
+}  // namespace android
diff --git a/jni/com_android_bluetooth_hid.cpp b/jni/com_android_bluetooth_hid_host.cpp
similarity index 98%
rename from jni/com_android_bluetooth_hid.cpp
rename to jni/com_android_bluetooth_hid_host.cpp
index 8a58c29..7838ff6 100644
--- a/jni/com_android_bluetooth_hid.cpp
+++ b/jni/com_android_bluetooth_hid_host.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "BluetoothHidServiceJni"
+#define LOG_TAG "BluetoothHidHostServiceJni"
 
 #define LOG_NDEBUG 1
 
@@ -514,8 +514,9 @@
     {"setIdleTimeNative", "([BB)Z", (void*)setIdleTimeNative},
 };
 
-int register_com_android_bluetooth_hid(JNIEnv* env) {
-  return jniRegisterNativeMethods(env, "com/android/bluetooth/hid/HidService",
+int register_com_android_bluetooth_hid_host(JNIEnv* env) {
+  return jniRegisterNativeMethods(env,
+                                  "com/android/bluetooth/hid/HidHostService",
                                   sMethods, NELEM(sMethods));
 }
-}
+}  // namespace android
diff --git a/jni/com_android_bluetooth_sdp.cpp b/jni/com_android_bluetooth_sdp.cpp
index f658413..c2eb5ce 100644
--- a/jni/com_android_bluetooth_sdp.cpp
+++ b/jni/com_android_bluetooth_sdp.cpp
@@ -25,29 +25,13 @@
 
 #include <string.h>
 
-static const uint8_t UUID_OBEX_OBJECT_PUSH[] = {
-    0x00, 0x00, 0x11, 0x05, 0x00, 0x00, 0x10, 0x00,
-    0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB};
-static const uint8_t UUID_PBAP_PSE[] = {0x00, 0x00, 0x11, 0x2F, 0x00, 0x00,
-                                        0x10, 0x00, 0x80, 0x00, 0x00, 0x80,
-                                        0x5F, 0x9B, 0x34, 0xFB};
-static const uint8_t UUID_MAP_MAS[] = {0x00, 0x00, 0x11, 0x32, 0x00, 0x00,
-                                       0x10, 0x00, 0x80, 0x00, 0x00, 0x80,
-                                       0x5F, 0x9B, 0x34, 0xFB};
-static const uint8_t UUID_MAP_MNS[] = {0x00, 0x00, 0x11, 0x33, 0x00, 0x00,
-                                       0x10, 0x00, 0x80, 0x00, 0x00, 0x80,
-                                       0x5F, 0x9B, 0x34, 0xFB};
-static const uint8_t UUID_SAP[] = {0x00, 0x00, 0x11, 0x2D, 0x00, 0x00,
-                                   0x10, 0x00, 0x80, 0x00, 0x00, 0x80,
-                                   0x5F, 0x9B, 0x34, 0xFB};
-// TODO:
-// Both the fact that the UUIDs are declared in multiple places, plus the fact
-// that there is a mess of UUID comparison and shortening methods will have to
-// be fixed.
-// The btcore->uuid module should be used for all instances.
+using bluetooth::Uuid;
 
-#define UUID_MAX_LENGTH 16
-#define IS_UUID(u1, u2) !memcmp(u1, u2, UUID_MAX_LENGTH)
+static const Uuid UUID_OBEX_OBJECT_PUSH = Uuid::From16Bit(0x1105);
+static const Uuid UUID_PBAP_PSE = Uuid::From16Bit(0x112F);
+static const Uuid UUID_MAP_MAS = Uuid::From16Bit(0x1132);
+static const Uuid UUID_MAP_MNS = Uuid::From16Bit(0x1133);
+static const Uuid UUID_SAP = Uuid::From16Bit(0x112D);
 
 namespace android {
 static jmethodID method_sdpRecordFoundCallback;
@@ -59,8 +43,8 @@
 
 static const btsdp_interface_t* sBluetoothSdpInterface = NULL;
 
-static void sdp_search_callback(bt_status_t status, RawAddress* bd_addr,
-                                uint8_t* uuid_in, int record_size,
+static void sdp_search_callback(bt_status_t status, const RawAddress& bd_addr,
+                                const Uuid& uuid_in, int record_size,
                                 bluetooth_sdp_record* record);
 
 btsdp_callbacks_t sBluetoothSdpCallbacks = {sizeof(sBluetoothSdpCallbacks),
@@ -135,8 +119,8 @@
   }
   ALOGD("%s UUID %.*s", __func__, 16, (uint8_t*)uuid);
 
-  int ret = sBluetoothSdpInterface->sdp_search((RawAddress*)addr,
-                                               (const uint8_t*)uuid);
+  int ret = sBluetoothSdpInterface->sdp_search(
+      (RawAddress*)addr, Uuid::From128BitBE((uint8_t*)uuid));
   if (ret != BT_STATUS_SUCCESS) {
     ALOGE("SDP Search initialization failed: %d", ret);
   }
@@ -146,8 +130,8 @@
   return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
-static void sdp_search_callback(bt_status_t status, RawAddress* bd_addr,
-                                uint8_t* uuid_in, int count,
+static void sdp_search_callback(bt_status_t status, const RawAddress& bd_addr,
+                                const Uuid& uuid_in, int count,
                                 bluetooth_sdp_record* records) {
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
@@ -156,14 +140,14 @@
       sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
   if (!addr.get()) return;
 
-  ScopedLocalRef<jbyteArray> uuid(
-      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(bt_uuid_t)));
+  ScopedLocalRef<jbyteArray> uuid(sCallbackEnv.get(),
+                                  sCallbackEnv->NewByteArray(sizeof(Uuid)));
   if (!uuid.get()) return;
 
   sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
-                                   (jbyte*)bd_addr);
-  sCallbackEnv->SetByteArrayRegion(uuid.get(), 0, sizeof(bt_uuid_t),
-                                   (jbyte*)uuid_in);
+                                   (const jbyte*)&bd_addr);
+  sCallbackEnv->SetByteArrayRegion(uuid.get(), 0, sizeof(Uuid),
+                                   (const jbyte*)uuid_in.To128BitBE().data());
 
   ALOGD("%s: Status is: %d, Record count: %d", __func__, status, count);
 
@@ -179,7 +163,7 @@
     }
 
     /* call the right callback according to the uuid*/
-    if (IS_UUID(UUID_MAP_MAS, uuid_in)) {
+    if (uuid_in == UUID_MAP_MAS) {
       sCallbackEnv->CallVoidMethod(
           sCallbacksObj, method_sdpMasRecordFoundCallback, (jint)status,
           addr.get(), uuid.get(), (jint)record->mas.mas_instance_id,
@@ -190,7 +174,7 @@
           (jint)record->mas.supported_message_types, service_name.get(),
           more_results);
 
-    } else if (IS_UUID(UUID_MAP_MNS, uuid_in)) {
+    } else if (uuid_in == UUID_MAP_MNS) {
       sCallbackEnv->CallVoidMethod(
           sCallbacksObj, method_sdpMnsRecordFoundCallback, (jint)status,
           addr.get(), uuid.get(), (jint)record->mns.hdr.l2cap_psm,
@@ -199,7 +183,7 @@
           (jint)record->mns.supported_features, service_name.get(),
           more_results);
 
-    } else if (IS_UUID(UUID_PBAP_PSE, uuid_in)) {
+    } else if (uuid_in == UUID_PBAP_PSE) {
       sCallbackEnv->CallVoidMethod(
           sCallbacksObj, method_sdpPseRecordFoundCallback, (jint)status,
           addr.get(), uuid.get(), (jint)record->pse.hdr.l2cap_psm,
@@ -209,7 +193,7 @@
           (jint)record->pse.supported_repositories, service_name.get(),
           more_results);
 
-    } else if (IS_UUID(UUID_OBEX_OBJECT_PUSH, uuid_in)) {
+    } else if (uuid_in == UUID_OBEX_OBJECT_PUSH) {
       jint formats_list_size = record->ops.supported_formats_list_len;
       ScopedLocalRef<jbyteArray> formats_list(
           sCallbackEnv.get(), sCallbackEnv->NewByteArray(formats_list_size));
@@ -225,7 +209,7 @@
           (jint)record->ops.hdr.profile_version, service_name.get(),
           formats_list.get(), more_results);
 
-    } else if (IS_UUID(UUID_SAP, uuid_in)) {
+    } else if (uuid_in == UUID_SAP) {
       sCallbackEnv->CallVoidMethod(
           sCallbacksObj, method_sdpSapsRecordFoundCallback, (jint)status,
           addr.get(), uuid.get(), (jint)record->mas.hdr.rfcomm_channel_number,
diff --git a/jni/permission_helpers.cc b/jni/permission_helpers.cc
new file mode 100644
index 0000000..26a8a89
--- /dev/null
+++ b/jni/permission_helpers.cc
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#include "permission_helpers.h"
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include "IUserManager.h"
+
+using ::android::binder::Status;
+
+namespace android {
+namespace bluetooth {
+
+uid_t foregroundUserId;
+uid_t systemUiUid;
+static uid_t SYSTEM_UID = 1000;
+constexpr int PER_USER_RANGE = 100000;
+
+Status checkPermission(const char* permission) {
+  int32_t pid;
+  int32_t uid;
+
+  if (android::checkCallingPermission(String16(permission), &pid, &uid)) {
+    return Status::ok();
+  }
+
+  auto err = ::base::StringPrintf("UID %d / PID %d lacks permission %s", uid,
+                                  pid, permission);
+  return Status::fromExceptionCode(Status::EX_SECURITY, String8(err.c_str()));
+}
+
+bool isCallerActiveUser() {
+  IPCThreadState* ipcState = IPCThreadState::selfOrNull();
+  if (!ipcState) return true;  // It's a local call
+
+  uid_t callingUid = ipcState->getCallingUid();
+  uid_t callingUser = callingUid / PER_USER_RANGE;
+  if (callingUid == getuid()) return true;  // It's a local call
+
+  return (foregroundUserId == callingUser) || (systemUiUid == callingUid) ||
+         (SYSTEM_UID == callingUid);
+}
+
+bool isCallerActiveUserOrManagedProfile() {
+  IPCThreadState* ipcState = IPCThreadState::selfOrNull();
+  if (!ipcState) return true;  // It's a local call
+
+  uid_t callingUid = ipcState->getCallingUid();
+  uid_t callingUser = callingUid / PER_USER_RANGE;
+  if (callingUid == getuid()) return true;  // It's a local call
+
+  if ((foregroundUserId == callingUser) || (systemUiUid == callingUid) ||
+      (SYSTEM_UID == callingUid))
+    return true;
+
+  uid_t parentUser = callingUser;
+
+  sp<IServiceManager> sm = defaultServiceManager();
+  sp<IBinder> binder = sm->getService(String16("user"));
+  sp<IUserManager> um = interface_cast<IUserManager>(binder);
+  if (um != NULL) {
+    // Must use Bluetooth process identity when making call to get parent user
+    int64_t ident = ipcState->clearCallingIdentity();
+    parentUser = um->getProfileParentId(callingUser);
+    ipcState->restoreCallingIdentity(ident);
+  }
+
+  return foregroundUserId == parentUser;
+}
+
+}  // namespace bluetooth
+}  // namespace android
diff --git a/jni/permission_helpers.h b/jni/permission_helpers.h
new file mode 100644
index 0000000..952281f
--- /dev/null
+++ b/jni/permission_helpers.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 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.
+ */
+#pragma once
+
+#include <binder/Status.h>
+
+namespace android {
+namespace bluetooth {
+
+const char PERMISSION_BLUETOOTH[] = "android.permission.BLUETOOTH";
+const char PERMISSION_BLUETOOTH_ADMIN[] = "android.permission.BLUETOOTH_ADMIN";
+const char PERMISSION_BLUETOOTH_PRIVILEGED[] =
+    "android.permission.BLUETOOTH_PRIVILEGED";
+
+extern uid_t foregroundUserId;
+extern uid_t systemUiUid;
+
+android::binder::Status checkPermission(const char* permission);
+bool isCallerActiveUser();
+bool isCallerActiveUserOrManagedProfile();
+
+}  // namespace bluetooth
+}  // namespace android
+
+#define ENFORCE_PERMISSION(permission)                     \
+  {                                                        \
+    android::binder::Status status =                       \
+        android::bluetooth::checkPermission((permission)); \
+    if (!status.isOk()) {                                  \
+      return status;                                       \
+    }                                                      \
+  }
\ No newline at end of file
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
index 6a40a27..b959858 100644
--- a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapContract.java
@@ -43,13 +43,13 @@
  * this interface, the {@code provider} tag for the Bluetooth related content provider must
  * have an intent-filter like the following in the manifest:
  * <pre class="prettyprint">&lt;provider  android:authorities="[PROVIDER AUTHORITY]"
-              android:exported="true"
-              android:enabled="true"
-              android:permission="android.permission.BLUETOOTH_MAP"&gt;
+ *             android:exported="true"
+ *             android:enabled="true"
+ *             android:permission="android.permission.BLUETOOTH_MAP"&gt;
  *   ...
  *      &lt;intent-filter&gt;
-           &lt;action android:name="android.content.action.BLEUETOOT_MAP_PROVIDER" /&gt;
-        &lt;/intent-filter&gt;
+ *          &lt;action android:name="android.content.action.BLEUETOOT_MAP_PROVIDER" /&gt;
+ *       &lt;/intent-filter&gt;
  *   ...
  *   &lt;/provider&gt;
  * [PROVIDER AUTHORITY] shall be the providers authority value which implements this
@@ -60,7 +60,7 @@
     /**
      * Constructor - should not be used
      */
-    private BluetoothMapContract(){
+    private BluetoothMapContract() {
       /* class should not be instantiated */
     }
 
@@ -162,8 +162,11 @@
      */
     public static Uri buildAccountUri(String authority) {
         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(authority).appendPath(TABLE_ACCOUNT).build();
+                .authority(authority)
+                .appendPath(TABLE_ACCOUNT)
+                .build();
     }
+
     /**
      * Build URI representing the given Account data-set with specific Id in a
      * Bluetooth provider. When queried, the direct URI for the account
@@ -176,6 +179,7 @@
                 .appendPath(accountId)
                 .build();
     }
+
     /**
      * Build URI representing the entire Message table in a
      * Bluetooth provider.
@@ -186,6 +190,7 @@
                 .appendPath(TABLE_MESSAGE)
                 .build();
     }
+
     /**
      * Build URI representing the given Message data-set in a
      * Bluetooth provider. When queried, the URI for the Messages
@@ -198,12 +203,13 @@
                 .appendPath(TABLE_MESSAGE)
                 .build();
     }
+
     /**
      * Build URI representing the given Message data-set with specific messageId in a
      * Bluetooth provider. When queried, the direct URI for the account
      * with the given accountID is returned.
      */
-    public static Uri buildMessageUriWithId(String authority, String accountId,String messageId) {
+    public static Uri buildMessageUriWithId(String authority, String accountId, String messageId) {
         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(authority)
                 .appendPath(accountId)
@@ -211,6 +217,7 @@
                 .appendPath(messageId)
                 .build();
     }
+
     /**
      * Build URI representing the given Message data-set in a
      * Bluetooth provider. When queried, the direct URI for the folder
@@ -261,6 +268,7 @@
                 .appendPath(TABLE_CONVOCONTACT)
                 .build();
     }
+
     /**
      * Build URI representing the given Contact data-set in a
      * Bluetooth provider. When queried, the direct URI for the contact
@@ -275,15 +283,16 @@
                 .appendPath(contactId)
                 .build();
     }
+
     /**
      *  @hide
      */
-    public static final String TABLE_ACCOUNT        = "Account";
-    public static final String TABLE_MESSAGE        = "Message";
-    public static final String TABLE_MESSAGE_PART   = "Part";
-    public static final String TABLE_FOLDER         = "Folder";
-    public static final String TABLE_CONVERSATION   = "Conversation";
-    public static final String TABLE_CONVOCONTACT   = "ConvoContact";
+    public static final String TABLE_ACCOUNT = "Account";
+    public static final String TABLE_MESSAGE = "Message";
+    public static final String TABLE_MESSAGE_PART = "Part";
+    public static final String TABLE_FOLDER = "Folder";
+    public static final String TABLE_CONVERSATION = "Conversation";
+    public static final String TABLE_CONVOCONTACT = "ConvoContact";
 
 
     /**
@@ -292,22 +301,22 @@
      * 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_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_OTHER = "OTHER";
 
     /**
      * Folder IDs to be used with Instant Messaging virtual folders
      */
-    public static final long FOLDER_ID_OTHER      = 0;
-    public static final long FOLDER_ID_INBOX      = 1;
-    public static final long FOLDER_ID_SENT       = 2;
-    public static final long FOLDER_ID_DRAFT      = 3;
-    public static final long FOLDER_ID_OUTBOX     = 4;
-    public static final long FOLDER_ID_DELETED    = 5;
+    public static final long FOLDER_ID_OTHER = 0;
+    public static final long FOLDER_ID_INBOX = 1;
+    public static final long FOLDER_ID_SENT = 2;
+    public static final long FOLDER_ID_DRAFT = 3;
+    public static final long FOLDER_ID_OUTBOX = 4;
+    public static final long FOLDER_ID_DELETED = 5;
 
 
     /**
@@ -380,7 +389,7 @@
          * The unique ID for a row.
          * <P>Type: INTEGER (long)</P>
          */
-        public static final String _ID = "_id";
+        String _ID = "_id";
 
         /**
          * The account name to display to the user on the device when selecting whether
@@ -395,7 +404,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String ACCOUNT_DISPLAY_NAME = "account_display_name";
+        String ACCOUNT_DISPLAY_NAME = "account_display_name";
 
         /**
          * Expose this account to other authenticated Bluetooth devices. If the expose flag
@@ -414,7 +423,7 @@
          *
          * <P>Type: INTEGER (boolean) hide = 0, show = 1</P>
          */
-        public static final String FLAG_EXPOSE = "flag_expose";
+        String FLAG_EXPOSE = "flag_expose";
 
 
         /**
@@ -428,7 +437,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String ACCOUNT_UCI = "account_uci";
+        String ACCOUNT_UCI = "account_uci";
 
 
         /**
@@ -444,7 +453,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String ACCOUNT_UCI_PREFIX = "account_uci_PREFIX";
+        String ACCOUNT_UCI_PREFIX = "account_uci_PREFIX";
 
     }
 
@@ -462,14 +471,14 @@
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String _ID = "_id";
+        String _ID = "_id";
         // FIXME add message parts for IM attachments
         /**
          * is this a text part  yes/no?
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String TEXT = "text";
+        String TEXT = "text";
 
         /**
          * The charset used in the content if it is text or 8BIT if it is
@@ -478,7 +487,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String CHARSET = "charset";
+        String CHARSET = "charset";
 
         /**
          * The filename representing the data file of the raw data in the database
@@ -489,7 +498,7 @@
          * read-only
          */
 
-        public static final String FILENAME = "filename";
+        String FILENAME = "filename";
 
         /**
          * Identifier for the content in the data. This can be used to
@@ -499,7 +508,7 @@
          * read-only
          */
 
-        public static final String CONTENT_ID = "cid";
+        String CONTENT_ID = "cid";
 
         /**
          * The raw data in either text format or binary format
@@ -507,9 +516,10 @@
          * <P>Type: BLOB</P>
          * read-only
          */
-        public static final String RAW_DATA = "raw_data";
+        String RAW_DATA = "raw_data";
 
     }
+
     /**
      * The actual message table containing all messages.
      * Content that must support filtering using WHERE clauses:
@@ -531,7 +541,7 @@
          * The unique ID for a row.
          * <P>Type: INTEGER (long)</P>
          */
-        public static final String _ID = "_id";
+        String _ID = "_id";
 
         /**
          * The date the message was received as a unix timestamp
@@ -540,7 +550,7 @@
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String DATE = "date";
+        String DATE = "date";
 
         //TODO REMOVE WHEN Parts Table is in place
         /**
@@ -548,59 +558,59 @@
          * <P>Type: TEXT</P>
          * read-only.
          */
-        public static final String BODY = "body";
+        String BODY = "body";
 
         /**
          * Message subject.
          * <P>Type: TEXT</P>
          * read-only.
          */
-        public static final String SUBJECT = "subject";
+        String SUBJECT = "subject";
 
         /**
          * Message Read flag
          * <P>Type: INTEGER (boolean) unread = 0, read = 1</P>
          *  read/write
          */
-        public static final String FLAG_READ = "flag_read";
+        String FLAG_READ = "flag_read";
 
         /**
          * Message Priority flag
          * <P>Type: INTEGER (boolean) normal priority = 0, high priority = 1</P>
          * read-only
          */
-        public static final String FLAG_HIGH_PRIORITY = "high_priority";
+        String FLAG_HIGH_PRIORITY = "high_priority";
 
         /**
          * Reception state - the amount of the message that have been loaded from the server.
          * <P>Type: TEXT see RECEPTION_STATE_* constants below </P>
          * read-only
          */
-        public static final String RECEPTION_STATE = "reception_state";
+        String RECEPTION_STATE = "reception_state";
 
         /**
          * Delivery state - the amount of the message that have been loaded from the server.
          * <P>Type: TEXT see DELIVERY_STATE_* constants below </P>
          * read-only
          */
-        public static final String DEVILERY_STATE = "delivery_state";
+        String DEVILERY_STATE = "delivery_state";
 
         /** To be able to filter messages with attachments, we need this flag.
          * <P>Type: INTEGER (boolean) no attachment = 0, attachment = 1 </P>
          * read-only
          */
-        public static final String FLAG_ATTACHMENT = "flag_attachment";
+        String FLAG_ATTACHMENT = "flag_attachment";
 
         /** The overall size in bytes of the attachments of the message.
          * <P>Type: INTEGER </P>
          */
-        public static final String ATTACHMENT_SIZE = "attachment_size";
+        String ATTACHMENT_SIZE = "attachment_size";
 
         /** The mine type of the attachments for the message.
          * <P>Type: TEXT </P>
          * read-only
          */
-        public static final String ATTACHMENT_MINE_TYPES = "attachment_mime_types";
+        String ATTACHMENT_MINE_TYPES = "attachment_mime_types";
 
         /** The overall size in bytes of the message including any attachments.
          * This value is informative only and should be the size an email client
@@ -608,13 +618,13 @@
          * <P>Type: INTEGER </P>
          * read-only
          */
-        public static final String MESSAGE_SIZE = "message_size";
+        String MESSAGE_SIZE = "message_size";
 
         /** Indicates that the message or a part of it is protected by a DRM scheme.
          * <P>Type: INTEGER (boolean) no DRM = 0, DRM protected = 1 </P>
          * read-only
          */
-        public static final String FLAG_PROTECTED = "flag_protected";
+        String FLAG_PROTECTED = "flag_protected";
 
         /**
          * A comma-delimited list of FROM addresses in RFC2822 format.
@@ -622,7 +632,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String FROM_LIST = "from_list";
+        String FROM_LIST = "from_list";
 
         /**
          * A comma-delimited list of TO addresses in RFC2822 format.
@@ -630,21 +640,21 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String TO_LIST = "to_list";
+        String TO_LIST = "to_list";
 
         /**
          * The unique ID for a row in the folder table in which this message belongs.
          * <P>Type: INTEGER (long)</P>
          * read/write
          */
-        public static final String FOLDER_ID = "folder_id";
+        String FOLDER_ID = "folder_id";
 
         /**
          * The unique ID for a row in the account table which owns this message.
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String ACCOUNT_ID = "account_id";
+        String ACCOUNT_ID = "account_id";
 
         /**
          * The ID identify the thread/conversation a message belongs to.
@@ -652,27 +662,26 @@
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String THREAD_ID = "thread_id";
+        String THREAD_ID = "thread_id";
 
         /**
          * The Name of the thread/conversation a message belongs to.
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String THREAD_NAME = "thread_name";
+        String THREAD_NAME = "thread_name";
     }
 
     public interface EmailMessageColumns {
 
 
-
         /**
          * A comma-delimited list of CC addresses in RFC2822 format.
          * The list must be compatible with Rfc822Tokenizer.tokenize();
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String CC_LIST = "cc_list";
+        String CC_LIST = "cc_list";
 
         /**
          * A comma-delimited list of BCC addresses in RFC2822 format.
@@ -680,7 +689,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String BCC_LIST = "bcc_list";
+        String BCC_LIST = "bcc_list";
 
         /**
          * A comma-delimited list of REPLY-TO addresses in RFC2822 format.
@@ -688,7 +697,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String REPLY_TO_LIST = "reply_to_List";
+        String REPLY_TO_LIST = "reply_to_List";
 
 
     }
@@ -737,28 +746,28 @@
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String _ID = "_id";
+        String _ID = "_id";
 
         /**
          * The folder display name to present to the user.
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String NAME = "name";
+        String NAME = "name";
 
         /**
          * The _id-key to the account this folder refers to.
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String ACCOUNT_ID = "account_id";
+        String ACCOUNT_ID = "account_id";
 
         /**
          * The _id-key to the parent folder. -1 for root folders.
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String PARENT_FOLDER_ID = "parent_id";
+        String PARENT_FOLDER_ID = "parent_id";
     }
 
     /**
@@ -793,7 +802,7 @@
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String THREAD_ID = "thread_id";
+        String THREAD_ID = "thread_id";
 
         /**
          * The unique ID for a row.
@@ -818,7 +827,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String THREAD_NAME = "thread_name";
+        String THREAD_NAME = "thread_name";
 
         /**
          * The time stamp of the last activity in the conversation as a unix timestamp
@@ -826,14 +835,14 @@
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String LAST_THREAD_ACTIVITY = "last_thread_activity";
+        String LAST_THREAD_ACTIVITY = "last_thread_activity";
 
         /**
          * The status on the conversation, either 'read' or 'unread'
          *  <P>Type: INTEGER (boolean) unread = 0, read = 1</P>
          * read/write
          */
-        public static final String READ_STATUS = "read_status";
+        String READ_STATUS = "read_status";
 
         /**
          * A counter that keep tack of version of the table content, count up on ID reuse
@@ -845,7 +854,7 @@
         //     BT-ON
         // UPDATE: TODO: Change to the last_activity time stamp (as a long value). This will
         //         provide the information needed for BT clients - currently unused
-        public static final String VERSION_COUNTER = "version_counter";
+        String VERSION_COUNTER = "version_counter";
 
         /**
          * A short description of the latest activity on conversation - typically
@@ -853,7 +862,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String SUMMARY = "convo_summary";
+        String SUMMARY = "convo_summary";
 
 
     }
@@ -874,25 +883,25 @@
 // Should not be needed anymore        public static final String _ID = "_id";
 
         /**
-        * The ID of the conversation the contact is part of.
-        * <P>Type: INTEGER (long)</P>
-        * read-only
-        */
-        public static final String CONVO_ID = "convo_id";
+         * The ID of the conversation the contact is part of.
+         * <P>Type: INTEGER (long)</P>
+         * read-only
+         */
+        String CONVO_ID = "convo_id";
 
         /**
          * The name of contact in instant message application
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String NAME = "name";
+        String NAME = "name";
 
         /**
          * The nickname of contact in instant message group chat conversation.
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String NICKNAME = "nickname";
+        String NICKNAME = "nickname";
 
 
         /**
@@ -900,7 +909,7 @@
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String X_BT_UID = "x_bt_uid";
+        String X_BT_UID = "x_bt_uid";
 
         /**
          * The unique ID for the contact within the domain of the interfacing service.
@@ -913,7 +922,7 @@
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String UCI = "x_bt_uci";
+        String UCI = "x_bt_uci";
     }
 
     /**
@@ -953,12 +962,12 @@
 
 
     public interface ChatState {
-        int UNKNOWN     = 0;
-        int INACITVE    = 1;
-        int ACITVE      = 2;
-        int COMPOSING   = 3;
-        int PAUSED      = 4;
-        int GONE        = 5;
+        int UNKNOWN = 0;
+        int INACITVE = 1;
+        int ACITVE = 2;
+        int COMPOSING = 3;
+        int PAUSED = 4;
+        int GONE = 5;
     }
 
     /**
@@ -990,7 +999,7 @@
          * <P>Type: INTERGER</P>
          * read-only
          */
-        public static final String CHAT_STATE = "chat_state";
+        String CHAT_STATE = "chat_state";
 
 //        /**
 //         * The geo location of the contact
@@ -1005,18 +1014,18 @@
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String LAST_ACTIVE = "last_active";
+        String LAST_ACTIVE = "last_active";
 
     }
 
     public interface PresenceState {
-        int UNKNOWN         = 0;
-        int OFFLINE         = 1;
-        int ONLINE          = 2;
-        int AWAY            = 3;
-        int DO_NOT_DISTURB  = 4;
-        int BUSY            = 5;
-        int IN_A_MEETING    = 6;
+        int UNKNOWN = 0;
+        int OFFLINE = 1;
+        int ONLINE = 2;
+        int AWAY = 3;
+        int DO_NOT_DISTURB = 4;
+        int BUSY = 5;
+        int IN_A_MEETING = 6;
     }
 
     /**
@@ -1040,7 +1049,7 @@
          * <P>Type: INTERGER</P>
          * read-only
          */
-        public static final String PRESENCE_STATE = "presence_state";
+        String PRESENCE_STATE = "presence_state";
 
         /**
          * The priority of contact presence
@@ -1048,21 +1057,21 @@
          * read-only
          */
 // TODO: IS THIS NEEDED - not in latest specification
-        public static final String PRIORITY = "priority";
+        String PRIORITY = "priority";
 
         /**
          * The last status text from contact
          * <P>Type: TEXT</P>
          * read-only
          */
-        public static final String STATUS_TEXT = "status_text";
+        String STATUS_TEXT = "status_text";
 
         /**
          * The time stamp of the last time the contact was online
          * <P>Type: INTEGER (long)</P>
          * read-only
          */
-        public static final String LAST_ONLINE = "last_online";
+        String LAST_ONLINE = "last_online";
 
     }
 
@@ -1070,149 +1079,146 @@
     /**
      * A projection of all the columns in the Message table
      */
-    public static final String[] BT_MESSAGE_PROJECTION = new String[] {
-        MessageColumns._ID,
-        MessageColumns.DATE,
-        MessageColumns.SUBJECT,
-        //TODO REMOVE WHEN Parts Table is in place
-        MessageColumns.BODY,
-        MessageColumns.MESSAGE_SIZE,
-        MessageColumns.FOLDER_ID,
-        MessageColumns.FLAG_READ,
-        MessageColumns.FLAG_PROTECTED,
-        MessageColumns.FLAG_HIGH_PRIORITY,
-        MessageColumns.FLAG_ATTACHMENT,
-        MessageColumns.ATTACHMENT_SIZE,
-        MessageColumns.FROM_LIST,
-        MessageColumns.TO_LIST,
-        MessageColumns.CC_LIST,
-        MessageColumns.BCC_LIST,
-        MessageColumns.REPLY_TO_LIST,
-        MessageColumns.RECEPTION_STATE,
-        MessageColumns.DEVILERY_STATE,
-        MessageColumns.THREAD_ID
+    public static final String[] BT_MESSAGE_PROJECTION = new String[]{
+            MessageColumns._ID,
+            MessageColumns.DATE,
+            MessageColumns.SUBJECT,
+            //TODO REMOVE WHEN Parts Table is in place
+            MessageColumns.BODY,
+            MessageColumns.MESSAGE_SIZE,
+            MessageColumns.FOLDER_ID,
+            MessageColumns.FLAG_READ,
+            MessageColumns.FLAG_PROTECTED,
+            MessageColumns.FLAG_HIGH_PRIORITY,
+            MessageColumns.FLAG_ATTACHMENT,
+            MessageColumns.ATTACHMENT_SIZE,
+            MessageColumns.FROM_LIST,
+            MessageColumns.TO_LIST,
+            MessageColumns.CC_LIST,
+            MessageColumns.BCC_LIST,
+            MessageColumns.REPLY_TO_LIST,
+            MessageColumns.RECEPTION_STATE,
+            MessageColumns.DEVILERY_STATE,
+            MessageColumns.THREAD_ID
     };
 
-    public static final String[] BT_INSTANT_MESSAGE_PROJECTION = new String[] {
-        MessageColumns._ID,
-        MessageColumns.DATE,
-        MessageColumns.SUBJECT,
-        MessageColumns.MESSAGE_SIZE,
-        MessageColumns.FOLDER_ID,
-        MessageColumns.FLAG_READ,
-        MessageColumns.FLAG_PROTECTED,
-        MessageColumns.FLAG_HIGH_PRIORITY,
-        MessageColumns.FLAG_ATTACHMENT,
-        MessageColumns.ATTACHMENT_SIZE,
-        MessageColumns.ATTACHMENT_MINE_TYPES,
-        MessageColumns.FROM_LIST,
-        MessageColumns.TO_LIST,
-        MessageColumns.RECEPTION_STATE,
-        MessageColumns.DEVILERY_STATE,
-        MessageColumns.THREAD_ID,
-        MessageColumns.THREAD_NAME
+    public static final String[] BT_INSTANT_MESSAGE_PROJECTION = new String[]{
+            MessageColumns._ID,
+            MessageColumns.DATE,
+            MessageColumns.SUBJECT,
+            MessageColumns.MESSAGE_SIZE,
+            MessageColumns.FOLDER_ID,
+            MessageColumns.FLAG_READ,
+            MessageColumns.FLAG_PROTECTED,
+            MessageColumns.FLAG_HIGH_PRIORITY,
+            MessageColumns.FLAG_ATTACHMENT,
+            MessageColumns.ATTACHMENT_SIZE,
+            MessageColumns.ATTACHMENT_MINE_TYPES,
+            MessageColumns.FROM_LIST,
+            MessageColumns.TO_LIST,
+            MessageColumns.RECEPTION_STATE,
+            MessageColumns.DEVILERY_STATE,
+            MessageColumns.THREAD_ID,
+            MessageColumns.THREAD_NAME
     };
 
     /**
      * A projection of all the columns in the Account table
      */
-    public static final String[] BT_ACCOUNT_PROJECTION = new String[] {
-        AccountColumns._ID,
-        AccountColumns.ACCOUNT_DISPLAY_NAME,
-        AccountColumns.FLAG_EXPOSE,
+    public static final String[] BT_ACCOUNT_PROJECTION = new String[]{
+            AccountColumns._ID, AccountColumns.ACCOUNT_DISPLAY_NAME, AccountColumns.FLAG_EXPOSE,
     };
 
     /**
      * A projection of all the columns in the Account table
      * TODO: Is this the way to differentiate
      */
-    public static final String[] BT_IM_ACCOUNT_PROJECTION = new String[] {
-        AccountColumns._ID,
-        AccountColumns.ACCOUNT_DISPLAY_NAME,
-        AccountColumns.FLAG_EXPOSE,
-        AccountColumns.ACCOUNT_UCI,
-        AccountColumns.ACCOUNT_UCI_PREFIX
+    public static final String[] BT_IM_ACCOUNT_PROJECTION = new String[]{
+            AccountColumns._ID,
+            AccountColumns.ACCOUNT_DISPLAY_NAME,
+            AccountColumns.FLAG_EXPOSE,
+            AccountColumns.ACCOUNT_UCI,
+            AccountColumns.ACCOUNT_UCI_PREFIX
     };
 
     /**
      * A projection of all the columns in the Folder table
      */
-    public static final String[] BT_FOLDER_PROJECTION = new String[] {
-        FolderColumns._ID,
-        FolderColumns.NAME,
-        FolderColumns.ACCOUNT_ID,
-        FolderColumns.PARENT_FOLDER_ID
+    public static final String[] BT_FOLDER_PROJECTION = new String[]{
+            FolderColumns._ID,
+            FolderColumns.NAME,
+            FolderColumns.ACCOUNT_ID,
+            FolderColumns.PARENT_FOLDER_ID
     };
 
 
     /**
      * A projection of all the columns in the Conversation table
      */
-    public static final String[] BT_CONVERSATION_PROJECTION = new String[] {
+    public static final String[] BT_CONVERSATION_PROJECTION = new String[]{
         /* Thread information */
-        ConversationColumns.THREAD_ID,
-        ConversationColumns.THREAD_NAME,
-        ConversationColumns.READ_STATUS,
-        ConversationColumns.LAST_THREAD_ACTIVITY,
-        ConversationColumns.VERSION_COUNTER,
-        ConversationColumns.SUMMARY,
+            ConversationColumns.THREAD_ID,
+            ConversationColumns.THREAD_NAME,
+            ConversationColumns.READ_STATUS,
+            ConversationColumns.LAST_THREAD_ACTIVITY,
+            ConversationColumns.VERSION_COUNTER,
+            ConversationColumns.SUMMARY,
         /* Contact information */
-        ConversationColumns.UCI,
-        ConversationColumns.NAME,
-        ConversationColumns.NICKNAME,
-        ConversationColumns.CHAT_STATE,
-        ConversationColumns.LAST_ACTIVE,
-        ConversationColumns.X_BT_UID,
-        ConversationColumns.PRESENCE_STATE,
-        ConversationColumns.STATUS_TEXT,
-        ConversationColumns.PRIORITY
+            ConversationColumns.UCI,
+            ConversationColumns.NAME,
+            ConversationColumns.NICKNAME,
+            ConversationColumns.CHAT_STATE,
+            ConversationColumns.LAST_ACTIVE,
+            ConversationColumns.X_BT_UID,
+            ConversationColumns.PRESENCE_STATE,
+            ConversationColumns.STATUS_TEXT,
+            ConversationColumns.PRIORITY
     };
 
     /**
      * A projection of the Contact Info and Presence columns in the Contact Info in table
      */
-    public static final String[] BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION = new String[] {
-        ConvoContactColumns.UCI,
-        ConvoContactColumns.CONVO_ID,
-        ConvoContactColumns.NAME,
-        ConvoContactColumns.NICKNAME,
-        ConvoContactColumns.X_BT_UID,
-        ConvoContactColumns.CHAT_STATE,
-        ConvoContactColumns.LAST_ACTIVE,
-        ConvoContactColumns.PRESENCE_STATE,
-        ConvoContactColumns.PRIORITY,
-        ConvoContactColumns.STATUS_TEXT,
-        ConvoContactColumns.LAST_ONLINE
+    public static final String[] BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION = new String[]{
+            ConvoContactColumns.UCI,
+            ConvoContactColumns.CONVO_ID,
+            ConvoContactColumns.NAME,
+            ConvoContactColumns.NICKNAME,
+            ConvoContactColumns.X_BT_UID,
+            ConvoContactColumns.CHAT_STATE,
+            ConvoContactColumns.LAST_ACTIVE,
+            ConvoContactColumns.PRESENCE_STATE,
+            ConvoContactColumns.PRIORITY,
+            ConvoContactColumns.STATUS_TEXT,
+            ConvoContactColumns.LAST_ONLINE
     };
 
     /**
      * A projection of the Contact Info the columns in Contacts Info table
      */
-    public static final String[] BT_CONTACT_PROJECTION = new String[] {
-        ConvoContactColumns.UCI,
-        ConvoContactColumns.CONVO_ID,
-        ConvoContactColumns.X_BT_UID,
-        ConvoContactColumns.NAME,
-        ConvoContactColumns.NICKNAME
+    public static final String[] BT_CONTACT_PROJECTION = new String[]{
+            ConvoContactColumns.UCI,
+            ConvoContactColumns.CONVO_ID,
+            ConvoContactColumns.X_BT_UID,
+            ConvoContactColumns.NAME,
+            ConvoContactColumns.NICKNAME
     };
 
 
     /**
      * A projection of all the columns in the Chat Status table
      */
-    public static final String[] BT_CHATSTATUS_PROJECTION = new String[] {
-        ChatStatusColumns.CHAT_STATE,
-        ChatStatusColumns.LAST_ACTIVE,
+    public static final String[] BT_CHATSTATUS_PROJECTION = new String[]{
+            ChatStatusColumns.CHAT_STATE, ChatStatusColumns.LAST_ACTIVE,
     };
 
     /**
      * A projection of all the columns in the Presence table
      */
-    public static final String[] BT_PRESENCE_PROJECTION = new String[] {
-        PresenceColumns.PRESENCE_STATE,
-        PresenceColumns.PRIORITY,
-        PresenceColumns.STATUS_TEXT,
-        PresenceColumns.LAST_ONLINE
+    public static final String[] BT_PRESENCE_PROJECTION = new String[]{
+            PresenceColumns.PRESENCE_STATE,
+            PresenceColumns.PRIORITY,
+            PresenceColumns.STATUS_TEXT,
+            PresenceColumns.LAST_ONLINE
     };
 
 }
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapEmailProvider.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapEmailProvider.java
index 23ccc70..629a10d 100644
--- a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapEmailProvider.java
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapEmailProvider.java
@@ -72,39 +72,38 @@
      * @param out the FileOurputStream to write to.
      * @throws IOException
      */
-    abstract protected void WriteMessageToStream(long accountId, long messageId,
-            boolean includeAttachment, boolean download, FileOutputStream out)
-        throws IOException;
+    protected abstract void WriteMessageToStream(long accountId, long messageId,
+            boolean includeAttachment, boolean download, FileOutputStream out) throws IOException;
 
     /**
      * @return the CONTENT_URI exposed. This will be used to send out notifications.
      */
-    abstract protected Uri getContentUri();
+    protected abstract Uri getContentUri();
 
-   /**
-    * Implementation is provided by the parent class.
-    */
-   @Override
-   public void attachInfo(Context context, ProviderInfo info) {
-       mAuthority = info.authority;
+    /**
+     * Implementation is provided by the parent class.
+     */
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+        mAuthority = info.authority;
 
-       mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-       mMatcher.addURI(mAuthority, BluetoothMapContract.TABLE_ACCOUNT, MATCH_ACCOUNT);
-       mMatcher.addURI(mAuthority, "#/"+BluetoothMapContract.TABLE_FOLDER, MATCH_FOLDER);
-       mMatcher.addURI(mAuthority, "#/"+BluetoothMapContract.TABLE_MESSAGE, MATCH_MESSAGE);
+        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+        mMatcher.addURI(mAuthority, BluetoothMapContract.TABLE_ACCOUNT, MATCH_ACCOUNT);
+        mMatcher.addURI(mAuthority, "#/" + BluetoothMapContract.TABLE_FOLDER, MATCH_FOLDER);
+        mMatcher.addURI(mAuthority, "#/" + BluetoothMapContract.TABLE_MESSAGE, MATCH_MESSAGE);
 
-       // Sanity check our setup
-       if (!info.exported) {
-           throw new SecurityException("Provider must be exported");
-       }
-       // Enforce correct permissions are used
-       if (!android.Manifest.permission.BLUETOOTH_MAP.equals(info.writePermission)){
-           throw new SecurityException("Provider must be protected by " +
-                   android.Manifest.permission.BLUETOOTH_MAP);
-       }
-       mResolver = context.getContentResolver();
-       super.attachInfo(context, info);
-   }
+        // Sanity check our setup
+        if (!info.exported) {
+            throw new SecurityException("Provider must be exported");
+        }
+        // Enforce correct permissions are used
+        if (!android.Manifest.permission.BLUETOOTH_MAP.equals(info.writePermission)) {
+            throw new SecurityException(
+                    "Provider must be protected by " + android.Manifest.permission.BLUETOOTH_MAP);
+        }
+        mResolver = context.getContentResolver();
+        super.attachInfo(context, info);
+    }
 
 
     /**
@@ -125,8 +124,8 @@
          * @param opts Options supplied by caller.
          * @param args Your own custom arguments.
          */
-        public void readDataFromPipe(ParcelFileDescriptor input, Uri uri, String mimeType,
-                Bundle opts, T args);
+        void readDataFromPipe(ParcelFileDescriptor input, Uri uri, String mimeType, Bundle opts,
+                T args);
     }
 
     public class PipeReader implements PipeDataReader<Cursor> {
@@ -135,8 +134,8 @@
          * Use the message to do an update of the message specified by the URI.
          */
         @Override
-        public void readDataFromPipe(ParcelFileDescriptor input, Uri uri,
-                String mimeType, Bundle opts, Cursor args) {
+        public void readDataFromPipe(ParcelFileDescriptor input, Uri uri, String mimeType,
+                Bundle opts, Cursor args) {
             Log.v(TAG, "readDataFromPipe(): uri=" + uri.toString());
             FileInputStream fIn = null;
             try {
@@ -145,16 +144,18 @@
                 long accountId = Long.valueOf(getAccountId(uri));
                 UpdateMimeMessageFromStream(fIn, accountId, messageId);
             } catch (IOException e) {
-                Log.w(TAG,"IOException: ", e);
-                /* TODO: How to signal the error to the calling entity? Had expected readDataFromPipe
+                Log.w(TAG, "IOException: ", e);
+                /* TODO: How to signal the error to the calling entity? Had expected
+                readDataFromPipe
                  *       to throw IOException?
                  */
             } finally {
                 try {
-                    if(fIn != null)
+                    if (fIn != null) {
                         fIn.close();
+                    }
                 } catch (IOException e) {
-                    Log.w(TAG,e);
+                    Log.w(TAG, e);
                 }
             }
         }
@@ -170,7 +171,7 @@
      * @param accountId the accountId
      * @param messageId ID of the message to update
      */
-    abstract protected void UpdateMimeMessageFromStream(FileInputStream input, long accountId,
+    protected abstract void UpdateMimeMessageFromStream(FileInputStream input, long accountId,
             long messageId) throws IOException;
 
     public class PipeWriter implements PipeDataWriter<Cursor> {
@@ -178,10 +179,13 @@
          * Generate a message based on the cursor, and write the encoded data to the stream.
          */
 
+        @Override
         public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
                 Bundle opts, Cursor c) {
-            if (D) Log.d(TAG, "writeDataToPipe(): uri=" + uri.toString() +
-                    " - getLastPathSegment() = " + uri.getLastPathSegment());
+            if (D) {
+                Log.d(TAG, "writeDataToPipe(): uri=" + uri.toString() + " - getLastPathSegment() = "
+                        + uri.getLastPathSegment());
+            }
 
             FileOutputStream fout = null;
 
@@ -193,14 +197,15 @@
                 List<String> segments = uri.getPathSegments();
                 long messageId = Long.parseLong(segments.get(2));
                 long accountId = Long.parseLong(getAccountId(uri));
-                if(segments.size() >= 4) {
+                if (segments.size() >= 4) {
                     String format = segments.get(3);
-                    if(format.equalsIgnoreCase(BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS)) {
+                    if (format.equalsIgnoreCase(BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS)) {
                         includeAttachments = false;
-                    } else if(format.equalsIgnoreCase(BluetoothMapContract.FILE_MSG_DOWNLOAD_NO_ATTACHMENTS)) {
+                    } else if (format.equalsIgnoreCase(
+                            BluetoothMapContract.FILE_MSG_DOWNLOAD_NO_ATTACHMENTS)) {
                         includeAttachments = false;
                         download = true;
-                    } else if(format.equalsIgnoreCase(BluetoothMapContract.FILE_MSG_DOWNLOAD)) {
+                    } else if (format.equalsIgnoreCase(BluetoothMapContract.FILE_MSG_DOWNLOAD)) {
                         download = true;
                     }
                 }
@@ -235,15 +240,17 @@
     protected void onAccountChanged(String accountId) {
         Uri newUri = null;
 
-        if(mAuthority == null){
+        if (mAuthority == null) {
             return;
         }
-        if(accountId == null){
+        if (accountId == null) {
             newUri = BluetoothMapContract.buildAccountUri(mAuthority);
         } else {
             newUri = BluetoothMapContract.buildAccountUriwithId(mAuthority, accountId);
         }
-        if(D) Log.d(TAG,"onAccountChanged() accountId = " + accountId + " URI: " + newUri);
+        if (D) {
+            Log.d(TAG, "onAccountChanged() accountId = " + accountId + " URI: " + newUri);
+        }
         mResolver.notifyChange(newUri, null);
     }
 
@@ -258,21 +265,24 @@
     protected void onMessageChanged(String accountId, String messageId) {
         Uri newUri = null;
 
-        if(mAuthority == null){
+        if (mAuthority == null) {
             return;
         }
 
-        if(accountId == null){
+        if (accountId == null) {
             newUri = BluetoothMapContract.buildMessageUri(mAuthority);
         } else {
-            if(messageId == null)
-            {
-                newUri = BluetoothMapContract.buildMessageUri(mAuthority,accountId);
+            if (messageId == null) {
+                newUri = BluetoothMapContract.buildMessageUri(mAuthority, accountId);
             } else {
-                newUri = BluetoothMapContract.buildMessageUriWithId(mAuthority,accountId, messageId);
+                newUri = BluetoothMapContract.buildMessageUriWithId(mAuthority, accountId,
+                        messageId);
             }
         }
-        if(D) Log.d(TAG,"onMessageChanged() accountId = " + accountId + " messageId = " + messageId + " URI: " + newUri);
+        if (D) {
+            Log.d(TAG, "onMessageChanged() accountId = " + accountId + " messageId = " + messageId
+                    + " URI: " + newUri);
+        }
         mResolver.notifyChange(newUri, null);
     }
 
@@ -317,18 +327,20 @@
     @Override
     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
         long callingId = Binder.clearCallingIdentity();
-        if(D)Log.d(TAG, "openFile(): uri=" + uri.toString() + " - getLastPathSegment() = " +
-                uri.getLastPathSegment());
+        if (D) {
+            Log.d(TAG, "openFile(): uri=" + uri.toString() + " - getLastPathSegment() = "
+                    + uri.getLastPathSegment());
+        }
         try {
             /* To be able to do abstraction of the file IO, we simply ignore the URI at this
              * point and let the read/write function implementations parse the URI. */
-            if(mode.equals("w")) {
+            if (mode.equals("w")) {
                 return openInversePipeHelper(uri, null, null, null, mPipeReader);
             } else {
-                return openPipeHelper (uri, null, null, null, mPipeWriter);
+                return openPipeHelper(uri, null, null, null, mPipeWriter);
             }
         } catch (IOException e) {
-            Log.w(TAG,e);
+            Log.w(TAG, e);
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
@@ -370,7 +382,7 @@
                     return null;
                 }
             };
-            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Object[])null);
+            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Object[]) null);
 
             return fds[1];
         } catch (IOException e) {
@@ -386,28 +398,35 @@
      */
     @Override
     public int delete(Uri uri, String where, String[] selectionArgs) {
-        if (D) Log.d(TAG, "delete(): uri=" + uri.toString() );
+        if (D) {
+            Log.d(TAG, "delete(): uri=" + uri.toString());
+        }
         int result = 0;
 
         String table = uri.getPathSegments().get(1);
-        if(table == null)
+        if (table == null) {
             throw new IllegalArgumentException("Table missing in URI");
+        }
         // the id of the entry to be deleted from the database
         String messageId = uri.getLastPathSegment();
-        if (messageId == null)
+        if (messageId == null) {
             throw new IllegalArgumentException("Message ID missing in update values!");
+        }
 
 
         String accountId = getAccountId(uri);
-        if (accountId == null)
+        if (accountId == null) {
             throw new IllegalArgumentException("Account ID missing in update values!");
+        }
 
         long callingId = Binder.clearCallingIdentity();
         try {
-            if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+            if (table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
                 return deleteMessage(accountId, messageId);
             } else {
-                if (D) Log.w(TAG, "Unknown table name: " + table);
+                if (D) {
+                    Log.w(TAG, "Unknown table name: " + table);
+                }
                 return result;
             }
         } finally {
@@ -421,7 +440,7 @@
      * @param messageId the ID of the message to delete.
      * @return the number of messages deleted - 0 if the message was not found.
      */
-    abstract protected int deleteMessage(String accountId, String messageId);
+    protected abstract int deleteMessage(String accountId, String messageId);
 
     /**
      * Insert is used to add new messages to the data base.
@@ -434,23 +453,25 @@
     @Override
     public Uri insert(Uri uri, ContentValues values) {
         String table = uri.getLastPathSegment();
-        if(table == null){
+        if (table == null) {
             throw new IllegalArgumentException("Table missing in URI");
         }
         String accountId = getAccountId(uri);
         Long folderId = values.getAsLong(BluetoothMapContract.MessageColumns.FOLDER_ID);
-        if(folderId == null) {
+        if (folderId == null) {
             throw new IllegalArgumentException("FolderId missing in ContentValues");
         }
 
         String id; // the id of the entry inserted into the database
         long callingId = Binder.clearCallingIdentity();
-        Log.d(TAG, "insert(): uri=" + uri.toString() + " - getLastPathSegment() = " +
-                uri.getLastPathSegment());
+        Log.d(TAG, "insert(): uri=" + uri.toString() + " - getLastPathSegment() = "
+                + uri.getLastPathSegment());
         try {
-            if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+            if (table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
                 id = insertMessage(accountId, folderId.toString());
-                if(D) Log.i(TAG, "insert() ID: " + id);
+                if (D) {
+                    Log.i(TAG, "insert() ID: " + id);
+                }
                 return Uri.parse(uri.toString() + "/" + id);
             } else {
                 Log.w(TAG, "Unknown table name: " + table);
@@ -469,9 +490,9 @@
      * @param folderId the ID of the folder to create a new message in.
      * @return the message id as a string
      */
-    abstract protected String insertMessage(String accountId, String folderId);
+    protected abstract String insertMessage(String accountId, String folderId);
 
-     /**
+    /**
      * Utility function to build a projection based on a projectionMap.
      *
      *   "btColumnName" -> "emailColumnName as btColumnName" for each entry.
@@ -481,16 +502,17 @@
      * @param projectionMap <btColumnName, emailColumnName>
      * @return the converted projection
      */
-    protected String[] convertProjection(String[] projection, Map<String,String> projectionMap) {
+    protected String[] convertProjection(String[] projection, Map<String, String> projectionMap) {
         String[] newProjection = new String[projection.length];
-        for(int i = 0; i < projection.length; i++) {
+        for (int i = 0; i < projection.length; i++) {
             newProjection[i] = projectionMap.get(projection[i]) + " as " + projection[i];
         }
         return newProjection;
     }
 
     /**
-     * This query needs to map from the data used in the e-mail client to BluetoothMapContract type of data.
+     * This query needs to map from the data used in the e-mail client to BluetoothMapContract
+     * type of data.
      */
     @Override
     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
@@ -525,8 +547,8 @@
      * @param sortOrder
      * @return a cursor to the accounts that are subject to exposure over BT.
      */
-    abstract protected Cursor queryAccount(String[] projection, String selection, String[] selectionArgs,
-            String sortOrder);
+    protected abstract Cursor queryAccount(String[] projection, String selection,
+            String[] selectionArgs, String sortOrder);
 
     /**
      * Filter out the non usable folders and ensure to name the mandatory folders
@@ -538,8 +560,9 @@
      * @param sortOrder
      * @return
      */
-    abstract protected Cursor queryFolder(String accountId, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder);
+    protected abstract Cursor queryFolder(String accountId, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder);
+
     /**
      * For the message table the selection (where clause) can only include the following columns:
      *    date: less than, greater than and equals
@@ -558,8 +581,8 @@
      * @param sortOrder
      * @return a cursor to query result
      */
-    abstract protected Cursor queryMessage(String accountId, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder);
+    protected abstract Cursor queryMessage(String accountId, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder);
 
     /**
      * update()
@@ -575,40 +598,47 @@
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
 
         String table = uri.getLastPathSegment();
-        if(table == null){
+        if (table == null) {
             throw new IllegalArgumentException("Table missing in URI");
         }
-        if(selection != null) {
-            throw new IllegalArgumentException("selection shall not be used, ContentValues shall contain the data");
+        if (selection != null) {
+            throw new IllegalArgumentException(
+                    "selection shall not be used, ContentValues shall contain the data");
         }
 
         long callingId = Binder.clearCallingIdentity();
-        if(D)Log.w(TAG, "update(): uri=" + uri.toString() + " - getLastPathSegment() = " +
-                uri.getLastPathSegment());
+        if (D) {
+            Log.w(TAG, "update(): uri=" + uri.toString() + " - getLastPathSegment() = "
+                    + uri.getLastPathSegment());
+        }
         try {
-            if(table.equals(BluetoothMapContract.TABLE_ACCOUNT)) {
+            if (table.equals(BluetoothMapContract.TABLE_ACCOUNT)) {
                 String accountId = values.getAsString(BluetoothMapContract.AccountColumns._ID);
-                if(accountId == null) {
+                if (accountId == null) {
                     throw new IllegalArgumentException("Account ID missing in update values!");
                 }
-                Integer exposeFlag = values.getAsInteger(BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
-                if(exposeFlag == null){
+                Integer exposeFlag =
+                        values.getAsInteger(BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
+                if (exposeFlag == null) {
                     throw new IllegalArgumentException("Expose flag missing in update values!");
                 }
                 return updateAccount(accountId, exposeFlag);
-            } else if(table.equals(BluetoothMapContract.TABLE_FOLDER)) {
+            } else if (table.equals(BluetoothMapContract.TABLE_FOLDER)) {
                 return 0; // We do not support changing folders
-            } else if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+            } else if (table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
                 String accountId = getAccountId(uri);
                 Long messageId = values.getAsLong(BluetoothMapContract.MessageColumns._ID);
-                if(messageId == null) {
+                if (messageId == null) {
                     throw new IllegalArgumentException("Message ID missing in update values!");
                 }
                 Long folderId = values.getAsLong(BluetoothMapContract.MessageColumns.FOLDER_ID);
-                Boolean flagRead = values.getAsBoolean(BluetoothMapContract.MessageColumns.FLAG_READ);
+                Boolean flagRead =
+                        values.getAsBoolean(BluetoothMapContract.MessageColumns.FLAG_READ);
                 return updateMessage(accountId, messageId, folderId, flagRead);
             } else {
-                if(D)Log.w(TAG, "Unknown table name: " + table);
+                if (D) {
+                    Log.w(TAG, "Unknown table name: " + table);
+                }
                 return 0;
             }
         } finally {
@@ -623,7 +653,7 @@
      * @param flagExpose the updated value.
      * @return the number of entries changed - 0 if account not found or value cannot be changed.
      */
-    abstract protected int updateAccount(String accountId, int flagExpose);
+    protected abstract int updateAccount(String accountId, int flagExpose);
 
     /**
      * Update an entry in the message table.
@@ -633,28 +663,32 @@
      * @param flagRead the new flagRead value to set - ignore if null.
      * @return
      */
-    abstract protected int updateMessage(String accountId, Long messageId, Long folderId, Boolean flagRead);
+    protected abstract int updateMessage(String accountId, Long messageId, Long folderId,
+            Boolean flagRead);
 
 
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
         long callingId = Binder.clearCallingIdentity();
-        if(D)Log.d(TAG, "call(): method=" + method + " arg=" + arg + "ThreadId: " + Thread.currentThread().getId());
+        if (D) {
+            Log.d(TAG, "call(): method=" + method + " arg=" + arg + "ThreadId: "
+                    + Thread.currentThread().getId());
+        }
 
         try {
-            if(method.equals(BluetoothMapContract.METHOD_UPDATE_FOLDER)) {
+            if (method.equals(BluetoothMapContract.METHOD_UPDATE_FOLDER)) {
                 long accountId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, -1);
-                if(accountId == -1) {
+                if (accountId == -1) {
                     Log.w(TAG, "No account ID in CALL");
                     return null;
                 }
                 long folderId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, -1);
-                if(folderId == -1) {
+                if (folderId == -1) {
                     Log.w(TAG, "No folder ID in CALL");
                     return null;
                 }
                 int ret = syncFolder(accountId, folderId);
-                if(ret == 0) {
+                if (ret == 0) {
                     return new Bundle();
                 }
                 return null;
@@ -671,7 +705,7 @@
      * @param folderId the ID of the folder.
      * @return 0 at success
      */
-    abstract protected int syncFolder(long accountId, long folderId);
+    protected abstract int syncFolder(long accountId, long folderId);
 
     /**
      * Need this to suppress warning in unit tests.
diff --git a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java
index cc3c311..dbc56ba 100644
--- a/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java
+++ b/lib/mapapi/com/android/bluetooth/mapapi/BluetoothMapIMProvider.java
@@ -56,37 +56,39 @@
     /**
      * @return the CONTENT_URI exposed. This will be used to send out notifications.
      */
-    abstract protected Uri getContentUri();
+    protected abstract Uri getContentUri();
 
     /**
      * Implementation is provided by the parent class.
      */
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
-       mAuthority = info.authority;
+        mAuthority = info.authority;
 
-       mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-       mMatcher.addURI(mAuthority, BluetoothMapContract.TABLE_ACCOUNT, MATCH_ACCOUNT);
-       mMatcher.addURI(mAuthority, "#/"+ BluetoothMapContract.TABLE_MESSAGE, MATCH_MESSAGE);
-       mMatcher.addURI(mAuthority, "#/"+ BluetoothMapContract.TABLE_CONVERSATION,
-               MATCH_CONVERSATION);
-       mMatcher.addURI(mAuthority, "#/"+ BluetoothMapContract.TABLE_CONVOCONTACT,
-               MATCH_CONVOCONTACT);
+        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+        mMatcher.addURI(mAuthority, BluetoothMapContract.TABLE_ACCOUNT, MATCH_ACCOUNT);
+        mMatcher.addURI(mAuthority, "#/" + BluetoothMapContract.TABLE_MESSAGE, MATCH_MESSAGE);
+        mMatcher.addURI(mAuthority, "#/" + BluetoothMapContract.TABLE_CONVERSATION,
+                MATCH_CONVERSATION);
+        mMatcher.addURI(mAuthority, "#/" + BluetoothMapContract.TABLE_CONVOCONTACT,
+                MATCH_CONVOCONTACT);
 
-       // Sanity check our setup
-       if (!info.exported) {
-           throw new SecurityException("Provider must be exported");
-       }
-       // Enforce correct permissions are used
-       if (!android.Manifest.permission.BLUETOOTH_MAP.equals(info.writePermission)){
-           throw new SecurityException("Provider must be protected by " +
-                   android.Manifest.permission.BLUETOOTH_MAP);
-       }
-       if(D) Log.d(TAG,"attachInfo() mAuthority = " + mAuthority);
+        // Sanity check our setup
+        if (!info.exported) {
+            throw new SecurityException("Provider must be exported");
+        }
+        // Enforce correct permissions are used
+        if (!android.Manifest.permission.BLUETOOTH_MAP.equals(info.writePermission)) {
+            throw new SecurityException(
+                    "Provider must be protected by " + android.Manifest.permission.BLUETOOTH_MAP);
+        }
+        if (D) {
+            Log.d(TAG, "attachInfo() mAuthority = " + mAuthority);
+        }
 
-       mResolver = context.getContentResolver();
-       super.attachInfo(context, info);
-   }
+        mResolver = context.getContentResolver();
+        super.attachInfo(context, info);
+    }
 
     /**
      * This function shall be called when any Account database content have changed
@@ -97,16 +99,18 @@
     protected void onAccountChanged(String accountId) {
         Uri newUri = null;
 
-        if(mAuthority == null){
+        if (mAuthority == null) {
             return;
         }
-        if(accountId == null){
+        if (accountId == null) {
             newUri = BluetoothMapContract.buildAccountUri(mAuthority);
         } else {
             newUri = BluetoothMapContract.buildAccountUriwithId(mAuthority, accountId);
         }
 
-        if(D) Log.d(TAG,"onAccountChanged() accountId = " + accountId + " URI: " + newUri);
+        if (D) {
+            Log.d(TAG, "onAccountChanged() accountId = " + accountId + " URI: " + newUri);
+        }
         mResolver.notifyChange(newUri, null);
     }
 
@@ -121,22 +125,23 @@
     protected void onMessageChanged(String accountId, String messageId) {
         Uri newUri = null;
 
-        if(mAuthority == null){
+        if (mAuthority == null) {
             return;
         }
-        if(accountId == null){
+        if (accountId == null) {
             newUri = BluetoothMapContract.buildMessageUri(mAuthority);
         } else {
-            if(messageId == null)
-            {
-                newUri = BluetoothMapContract.buildMessageUri(mAuthority,accountId);
+            if (messageId == null) {
+                newUri = BluetoothMapContract.buildMessageUri(mAuthority, accountId);
             } else {
-                newUri = BluetoothMapContract.buildMessageUriWithId(mAuthority,accountId,
+                newUri = BluetoothMapContract.buildMessageUriWithId(mAuthority, accountId,
                         messageId);
             }
         }
-        if(D) Log.d(TAG,"onMessageChanged() accountId = " + accountId
-                + " messageId = " + messageId + " URI: " + newUri);
+        if (D) {
+            Log.d(TAG, "onMessageChanged() accountId = " + accountId + " messageId = " + messageId
+                    + " URI: " + newUri);
+        }
         mResolver.notifyChange(newUri, null);
     }
 
@@ -152,22 +157,23 @@
     protected void onContactChanged(String accountId, String contactId) {
         Uri newUri = null;
 
-        if(mAuthority == null){
+        if (mAuthority == null) {
             return;
         }
-        if(accountId == null){
+        if (accountId == null) {
             newUri = BluetoothMapContract.buildConvoContactsUri(mAuthority);
         } else {
-            if(contactId == null)
-            {
-                newUri = BluetoothMapContract.buildConvoContactsUri(mAuthority,accountId);
+            if (contactId == null) {
+                newUri = BluetoothMapContract.buildConvoContactsUri(mAuthority, accountId);
             } else {
                 newUri = BluetoothMapContract.buildConvoContactsUriWithId(mAuthority, accountId,
                         contactId);
             }
         }
-        if(D) Log.d(TAG,"onContactChanged() accountId = " + accountId
-                + " contactId = " + contactId + " URI: " + newUri);
+        if (D) {
+            Log.d(TAG, "onContactChanged() accountId = " + accountId + " contactId = " + contactId
+                    + " URI: " + newUri);
+        }
         mResolver.notifyChange(newUri, null);
     }
 
@@ -188,27 +194,34 @@
      */
     @Override
     public int delete(Uri uri, String where, String[] selectionArgs) {
-        if (D) Log.d(TAG, "delete(): uri=" + uri.toString() );
+        if (D) {
+            Log.d(TAG, "delete(): uri=" + uri.toString());
+        }
         int result = 0;
 
         String table = uri.getPathSegments().get(1);
-        if(table == null)
+        if (table == null) {
             throw new IllegalArgumentException("Table missing in URI");
+        }
         // the id of the entry to be deleted from the database
         String messageId = uri.getLastPathSegment();
-        if (messageId == null)
+        if (messageId == null) {
             throw new IllegalArgumentException("Message ID missing in update values!");
+        }
 
         String accountId = getAccountId(uri);
-        if (accountId == null)
+        if (accountId == null) {
             throw new IllegalArgumentException("Account ID missing in update values!");
+        }
 
         long callingId = Binder.clearCallingIdentity();
         try {
-            if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+            if (table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
                 return deleteMessage(accountId, messageId);
             } else {
-                if (D) Log.w(TAG, "Unknown table name: " + table);
+                if (D) {
+                    Log.w(TAG, "Unknown table name: " + table);
+                }
                 return result;
             }
         } finally {
@@ -222,7 +235,7 @@
      * @param messageId the ID of the message to delete.
      * @return the number of messages deleted - 0 if the message was not found.
      */
-    abstract protected int deleteMessage(String accountId, String messageId);
+    protected abstract int deleteMessage(String accountId, String messageId);
 
     /**
      * Insert is used to add new messages to the data base.
@@ -235,23 +248,27 @@
     @Override
     public Uri insert(Uri uri, ContentValues values) {
         String table = uri.getLastPathSegment();
-        if(table == null)
+        if (table == null) {
             throw new IllegalArgumentException("Table missing in URI");
+        }
 
         String accountId = getAccountId(uri);
-        if (accountId == null)
+        if (accountId == null) {
             throw new IllegalArgumentException("Account ID missing in URI");
+        }
 
         // TODO: validate values?
 
         String id; // the id of the entry inserted into the database
         long callingId = Binder.clearCallingIdentity();
-        Log.d(TAG, "insert(): uri=" + uri.toString() + " - getLastPathSegment() = " +
-                uri.getLastPathSegment());
+        Log.d(TAG, "insert(): uri=" + uri.toString() + " - getLastPathSegment() = "
+                + uri.getLastPathSegment());
         try {
-            if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+            if (table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
                 id = insertMessage(accountId, values);
-                if(D) Log.i(TAG, "insert() ID: " + id);
+                if (D) {
+                    Log.i(TAG, "insert() ID: " + id);
+                }
                 return Uri.parse(uri.toString() + "/" + id);
             } else {
                 Log.w(TAG, "Unknown table name: " + table);
@@ -270,9 +287,9 @@
      * @param folderId the ID of the folder to create a new message in.
      * @return the message id as a string
      */
-    abstract protected String insertMessage(String accountId, ContentValues values);
+    protected abstract String insertMessage(String accountId, ContentValues values);
 
-     /**
+    /**
      * Utility function to build a projection based on a projectionMap.
      *
      *   "btColumnName" -> "imColumnName as btColumnName" for each entry.
@@ -282,9 +299,9 @@
      * @param projectionMap <string, string>
      * @return the converted projection
      */
-    protected String[] convertProjection(String[] projection, Map<String,String> projectionMap) {
+    protected String[] convertProjection(String[] projection, Map<String, String> projectionMap) {
         String[] newProjection = new String[projection.length];
-        for(int i = 0; i < projection.length; i++) {
+        for (int i = 0; i < projection.length; i++) {
             newProjection[i] = projectionMap.get(projection[i]) + " as " + projection[i];
         }
         return newProjection;
@@ -300,7 +317,9 @@
         long callingId = Binder.clearCallingIdentity();
         try {
             String accountId = null;
-            if(D)Log.w(TAG, "query(): uri =" + mAuthority + " uri=" + uri.toString());
+            if (D) {
+                Log.w(TAG, "query(): uri =" + mAuthority + " uri=" + uri.toString());
+            }
 
             switch (mMatcher.match(uri)) {
                 case MATCH_ACCOUNT:
@@ -316,22 +335,22 @@
                             uri.getQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING);
                     Long periodBegin = null;
                     value = uri.getQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN);
-                    if(value != null) {
+                    if (value != null) {
                         periodBegin = Long.parseLong(value);
                     }
                     Long periodEnd = null;
                     value = uri.getQueryParameter(BluetoothMapContract.FILTER_PERIOD_END);
-                    if(value != null) {
+                    if (value != null) {
                         periodEnd = Long.parseLong(value);
                     }
                     Boolean read = null;
                     value = uri.getQueryParameter(BluetoothMapContract.FILTER_READ_STATUS);
-                    if(value != null) {
+                    if (value != null) {
                         read = value.equalsIgnoreCase("true");
                     }
                     Long threadId = null;
                     value = uri.getQueryParameter(BluetoothMapContract.FILTER_THREAD_ID);
-                    if(value != null) {
+                    if (value != null) {
                         threadId = Long.parseLong(value);
                     }
                     return queryConversation(accountId, threadId, read, periodEnd, periodBegin,
@@ -339,8 +358,8 @@
                 case MATCH_CONVOCONTACT:
                     accountId = getAccountId(uri);
                     long contactId = 0;
-                    return queryConvoContact(accountId, contactId, projection,
-                            selection, selectionArgs, sortOrder);
+                    return queryConvoContact(accountId, contactId, projection, selection,
+                            selectionArgs, sortOrder);
                 default:
                     throw new UnsupportedOperationException("Unsupported Uri " + uri);
             }
@@ -359,7 +378,7 @@
      * @param sortOrder
      * @return a cursor to the accounts that are subject to exposure over BT.
      */
-    abstract protected Cursor queryAccount(String[] projection, String selection,
+    protected abstract Cursor queryAccount(String[] projection, String selection,
             String[] selectionArgs, String sortOrder);
 
     /**
@@ -378,7 +397,7 @@
      * @param sortOrder
      * @return a cursor to query result
      */
-    abstract protected Cursor queryMessage(String accountId, String[] projection, String selection,
+    protected abstract Cursor queryMessage(String accountId, String[] projection, String selection,
             String[] selectionArgs, String sortOrder);
 
     /**
@@ -410,7 +429,7 @@
      *        2 |         "" | ... |       Peter |               2 | ... |
      *        3 |         "" | ... |        Hans |               1 | ... |
      *
-    * @param accountId the ID of the account
+     * @param accountId the ID of the account
      * @param threadId filter on a single threadId - null if no filtering is needed.
      * @param read filter on a read status:
      *             null: no filtering on read is needed.
@@ -426,7 +445,7 @@
      * @param sortOrder  the sort order
      * @return a Cursor representing the query result.
      */
-    abstract protected Cursor queryConversation(String accountId, Long threadId, Boolean read,
+    protected abstract Cursor queryConversation(String accountId, Long threadId, Boolean read,
             Long periodEnd, Long periodBegin, String searchString, String[] projection,
             String sortOrder);
 
@@ -450,7 +469,7 @@
      * @param sortOrder
      * @return a cursor to query result
      */
-    abstract protected Cursor queryConvoContact(String accountId, Long contactId,
+    protected abstract Cursor queryConvoContact(String accountId, Long contactId,
             String[] projection, String selection, String[] selectionArgs, String sortOrder);
 
     /**
@@ -476,50 +495,54 @@
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
 
         String table = uri.getLastPathSegment();
-        if(table == null){
+        if (table == null) {
             throw new IllegalArgumentException("Table missing in URI");
         }
-        if(selection != null) {
-            throw new IllegalArgumentException("selection shall not be used, ContentValues " +
-                    "shall contain the data");
+        if (selection != null) {
+            throw new IllegalArgumentException(
+                    "selection shall not be used, ContentValues " + "shall contain the data");
         }
 
         long callingId = Binder.clearCallingIdentity();
-        if(D)Log.w(TAG, "update(): uri=" + uri.toString() + " - getLastPathSegment() = " +
-                uri.getLastPathSegment());
+        if (D) {
+            Log.w(TAG, "update(): uri=" + uri.toString() + " - getLastPathSegment() = "
+                    + uri.getLastPathSegment());
+        }
         try {
-            if(table.equals(BluetoothMapContract.TABLE_ACCOUNT)) {
+            if (table.equals(BluetoothMapContract.TABLE_ACCOUNT)) {
                 String accountId = values.getAsString(BluetoothMapContract.AccountColumns._ID);
-                if(accountId == null) {
+                if (accountId == null) {
                     throw new IllegalArgumentException("Account ID missing in update values!");
                 }
-                Integer exposeFlag = values.getAsInteger(
-                        BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
-                if(exposeFlag == null){
+                Integer exposeFlag =
+                        values.getAsInteger(BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
+                if (exposeFlag == null) {
                     throw new IllegalArgumentException("Expose flag missing in update values!");
                 }
                 return updateAccount(accountId, exposeFlag);
-            } else if(table.equals(BluetoothMapContract.TABLE_FOLDER)) {
+            } else if (table.equals(BluetoothMapContract.TABLE_FOLDER)) {
                 return 0; // We do not support changing folders
-            } else if(table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
+            } else if (table.equals(BluetoothMapContract.TABLE_MESSAGE)) {
                 String accountId = getAccountId(uri);
-                if(accountId == null) {
+                if (accountId == null) {
                     throw new IllegalArgumentException("Account ID missing in update values!");
                 }
                 Long messageId = values.getAsLong(BluetoothMapContract.MessageColumns._ID);
-                if(messageId == null) {
+                if (messageId == null) {
                     throw new IllegalArgumentException("Message ID missing in update values!");
                 }
                 Long folderId = values.getAsLong(BluetoothMapContract.MessageColumns.FOLDER_ID);
-                Boolean flagRead = values.getAsBoolean(
-                        BluetoothMapContract.MessageColumns.FLAG_READ);
+                Boolean flagRead =
+                        values.getAsBoolean(BluetoothMapContract.MessageColumns.FLAG_READ);
                 return updateMessage(accountId, messageId, folderId, flagRead);
-            } else if(table.equals(BluetoothMapContract.TABLE_CONVERSATION)) {
+            } else if (table.equals(BluetoothMapContract.TABLE_CONVERSATION)) {
                 return 0; // We do not support changing conversation
-            } else if(table.equals(BluetoothMapContract.TABLE_CONVOCONTACT)) {
+            } else if (table.equals(BluetoothMapContract.TABLE_CONVOCONTACT)) {
                 return 0; // We do not support changing contacts
             } else {
-                if(D)Log.w(TAG, "Unknown table name: " + table);
+                if (D) {
+                    Log.w(TAG, "Unknown table name: " + table);
+                }
                 return 0;
             }
         } finally {
@@ -534,7 +557,7 @@
      * @param flagExpose the updated value.
      * @return the number of entries changed - 0 if account not found or value cannot be changed.
      */
-    abstract protected int updateAccount(String accountId, Integer flagExpose);
+    protected abstract int updateAccount(String accountId, Integer flagExpose);
 
     /**
      * Update an entry in the message table.
@@ -544,7 +567,7 @@
      * @param flagRead the new flagRead value to set - ignore if null.
      * @return
      */
-    abstract protected int updateMessage(String accountId, Long messageId, Long folderId,
+    protected abstract int updateMessage(String accountId, Long messageId, Long folderId,
             Boolean flagRead);
 
     /**
@@ -556,31 +579,31 @@
      * @return a new ContentValues object with the keys replaced as specified in the
      * keyMap
      */
-    protected ContentValues createContentValues(Set<Entry<String,Object>> valueSet,
+    protected ContentValues createContentValues(Set<Entry<String, Object>> valueSet,
             Map<String, String> keyMap) {
         ContentValues values = new ContentValues(valueSet.size());
-        for(Entry<String,Object> ent : valueSet) {
+        for (Entry<String, Object> ent : valueSet) {
             String key = keyMap.get(ent.getKey()); // Convert the key name
             Object value = ent.getValue();
-            if(value == null) {
+            if (value == null) {
                 values.putNull(key);
-            } else if(ent.getValue() instanceof Boolean) {
+            } else if (ent.getValue() instanceof Boolean) {
                 values.put(key, (Boolean) value);
-            } else if(ent.getValue() instanceof Byte) {
+            } else if (ent.getValue() instanceof Byte) {
                 values.put(key, (Byte) value);
-            } else if(ent.getValue() instanceof byte[]) {
+            } else if (ent.getValue() instanceof byte[]) {
                 values.put(key, (byte[]) value);
-            } else if(ent.getValue() instanceof Double) {
+            } else if (ent.getValue() instanceof Double) {
                 values.put(key, (Double) value);
-            } else if(ent.getValue() instanceof Float) {
+            } else if (ent.getValue() instanceof Float) {
                 values.put(key, (Float) value);
-            } else if(ent.getValue() instanceof Integer) {
+            } else if (ent.getValue() instanceof Integer) {
                 values.put(key, (Integer) value);
-            } else if(ent.getValue() instanceof Long) {
+            } else if (ent.getValue() instanceof Long) {
                 values.put(key, (Long) value);
-            } else if(ent.getValue() instanceof Short) {
+            } else if (ent.getValue() instanceof Short) {
                 values.put(key, (Short) value);
-            } else if(ent.getValue() instanceof String) {
+            } else if (ent.getValue() instanceof String) {
                 values.put(key, (String) value);
             } else {
                 throw new IllegalArgumentException("Unknown data type in content value");
@@ -592,40 +615,42 @@
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
         long callingId = Binder.clearCallingIdentity();
-        if(D)Log.w(TAG, "call(): method=" + method + " arg=" + arg + "ThreadId: "
-                + Thread.currentThread().getId());
+        if (D) {
+            Log.w(TAG, "call(): method=" + method + " arg=" + arg + "ThreadId: "
+                    + Thread.currentThread().getId());
+        }
         int ret = -1;
         try {
-            if(method.equals(BluetoothMapContract.METHOD_UPDATE_FOLDER)) {
+            if (method.equals(BluetoothMapContract.METHOD_UPDATE_FOLDER)) {
                 long accountId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, -1);
-                if(accountId == -1) {
+                if (accountId == -1) {
                     Log.w(TAG, "No account ID in CALL");
                     return null;
                 }
                 long folderId = extras.getLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, -1);
-                if(folderId == -1) {
+                if (folderId == -1) {
                     Log.w(TAG, "No folder ID in CALL");
                     return null;
                 }
                 ret = syncFolder(accountId, folderId);
             } else if (method.equals(BluetoothMapContract.METHOD_SET_OWNER_STATUS)) {
                 int presenceState = extras.getInt(BluetoothMapContract.EXTRA_PRESENCE_STATE);
-                String presenceStatus = extras.getString(
-                        BluetoothMapContract.EXTRA_PRESENCE_STATUS);
+                String presenceStatus =
+                        extras.getString(BluetoothMapContract.EXTRA_PRESENCE_STATUS);
                 long lastActive = extras.getLong(BluetoothMapContract.EXTRA_LAST_ACTIVE);
                 int chatState = extras.getInt(BluetoothMapContract.EXTRA_CHAT_STATE);
                 String convoId = extras.getString(BluetoothMapContract.EXTRA_CONVERSATION_ID);
                 ret = setOwnerStatus(presenceState, presenceStatus, lastActive, chatState, convoId);
 
             } else if (method.equals(BluetoothMapContract.METHOD_SET_BLUETOOTH_STATE)) {
-                boolean bluetoothState = extras.getBoolean(
-                        BluetoothMapContract.EXTRA_BLUETOOTH_STATE);
+                boolean bluetoothState =
+                        extras.getBoolean(BluetoothMapContract.EXTRA_BLUETOOTH_STATE);
                 ret = setBluetoothStatus(bluetoothState);
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
-        if(ret == 0) {
+        if (ret == 0) {
             return new Bundle();
         }
         return null;
@@ -637,7 +662,7 @@
      * @param folderId the ID of the folder.
      * @return 0 at success
      */
-    abstract protected int syncFolder(long accountId, long folderId);
+    protected abstract int syncFolder(long accountId, long folderId);
 
     /**
      * Set the properties that should change presence or chat state of owner
@@ -650,16 +675,15 @@
      * @param convoId ID to the conversation to change
      * @return 0 at success
      */
-    abstract protected int setOwnerStatus(int presenceState, String presenceStatus,
-            long lastActive, int chatState, String convoId);
+    protected abstract int setOwnerStatus(int presenceState, String presenceStatus, long lastActive,
+            int chatState, String convoId);
 
     /**
      * Notify the application of the Bluetooth state
      * @param bluetoothState 'on' of 'off'
      * @return 0 at success
      */
-    abstract protected int setBluetoothStatus(boolean bluetoothState);
-
+    protected abstract int setBluetoothStatus(boolean bluetoothState);
 
 
     /**
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 296e54d..ce320e1 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-oudio is gekoppel"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-oudio is ontkoppel"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-oudio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Lêers groter as 4 GB kan nie oorgedra word nie"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index f4b2bb1..0f40380 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -132,4 +132,5 @@
     <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">"ከ4 ጊባ በላይ የሆኑ ፋይሎች ሊዛወሩ አይችሉም"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 68d0e06..cc7d0b6 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>
@@ -140,4 +140,5 @@
     <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">"يتعذّر نقل الملفات التي يزيد حجمها عن 4 غيغابايت"</string>
 </resources>
diff --git a/res/values-as/config.xml b/res/values-as/config.xml
new file mode 100644
index 0000000..4f96544
--- /dev/null
+++ b/res/values-as/config.xml
@@ -0,0 +1,19 @@
+<?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-as/strings.xml b/res/values-as/strings.xml
new file mode 100644
index 0000000..bfb3168
--- /dev/null
+++ b/res/values-as/strings.xml
@@ -0,0 +1,136 @@
+<?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="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_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_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>\"ৰ পৰা লাভ কৰা ফাইলটো ছেভ কৰিবলৈ ইউএছবি সঞ্চয়াগাৰত পৰ্যাপ্ত খালী ঠাই নাই"</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_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">"কোনো ইউএছবি সঞ্চয়াগাৰ নাই।"</string>
+    <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"কোনো এছডি কাৰ্ড নাই। স্থানান্তৰ কৰা ফাইলসমূহ ছেভ কৰিবলৈ এছডি কাৰ্ড ভৰাওক।"</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="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>
+    </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">"একাউণ্ট বাছনি কৰিব নোৱাৰি। ০টা শ্লটবোৰ বাকী আছে"</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-as/strings_pbap.xml b/res/values-as/strings_pbap.xml
new file mode 100644
index 0000000..f59084c
--- /dev/null
+++ b/res/values-as/strings_pbap.xml
@@ -0,0 +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">"%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">"০০০০০০"</string>
+    <string name="pbap_notification_group" msgid="8487669554703627168">"ব্লুটুথ সম্পৰ্ক শ্বেয়াৰ"</string>
+</resources>
diff --git a/res/values-as/strings_pbap_client.xml b/res/values-as/strings_pbap_client.xml
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-as/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?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-as/strings_sap.xml b/res/values-as/strings_sap.xml
new file mode 100644
index 0000000..1642cdf
--- /dev/null
+++ b/res/values-as/strings_sap.xml
@@ -0,0 +1,10 @@
+<?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">"ব্লুটুথৰ ছিম ব্যৱহাৰ"</string>
+    <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"ব্লুটুথৰ ছিম ব্যৱহাৰ"</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-as/test_strings.xml b/res/values-as/test_strings.xml
new file mode 100644
index 0000000..a2efd87
--- /dev/null
+++ b/res/values-as/test_strings.xml
@@ -0,0 +1,13 @@
+<?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-az/strings.xml b/res/values-az/strings.xml
index 792c6cb..4511191 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio bağlantısı yaradıldı"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio ilə bağlantı kəsildi"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB-dən böyük olan faylları köçürmək mümkün deyil"</string>
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 87bdeeb..9f8735b 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio je povezan"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Veza sa Bluetooth audijom je prekinuta"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Ne mogu da se prenose datoteke veće od 4 GB"</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 2173367..98b6477 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -136,4 +136,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Немагчыма перадаць файлы, большыя за 4 ГБ"</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index faee3c4..db51d2b 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -132,4 +132,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Файловете с размер над 4 ГБ не могат да бъдат прехвърлени"</string>
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 7dfd8e9..5e4fbc9 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -132,4 +132,5 @@
     <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">"৪GB থেকে বড় ফটো ট্রান্সফার করা যাবে না"</string>
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 9aa9c91..3dc12a2 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 preko Bluetootha"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Primljeno putem 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>
@@ -134,4 +134,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio je povezan"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio je isključen"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Nije moguće prenijeti fajlove veće od 4 GB"</string>
 </resources>
diff --git a/res/values-bs/strings_pbap.xml b/res/values-bs/strings_pbap.xml
index 5f35408..55bbe3c 100644
--- a/res/values-bs/strings_pbap.xml
+++ b/res/values-bs/strings_pbap.xml
@@ -5,7 +5,7 @@
     <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"Neophodan je ključ za Bluetooth sesiju"</string>
     <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"Isteklo je vrijeme za prihvatanje veze sa uređajem %1$s"</string>
     <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"Isteklo je vrijeme za unošenje ključa sesije sa uređajem %1$s"</string>
-    <string name="auth_notif_ticker" msgid="1575825798053163744">"Zahtjev za Obex provjeru vjerodostojnosti"</string>
+    <string name="auth_notif_ticker" msgid="1575825798053163744">"Zahtjev za Obex autentifikaciju"</string>
     <string name="auth_notif_title" msgid="7599854855681573258">"Ključ sesije"</string>
     <string name="auth_notif_message" msgid="6667218116427605038">"Unesite ključ sesije za %1$s"</string>
     <string name="defaultname" msgid="4821590500649090078">"Komplet za automobil"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 06426c2..f77534d 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 del fitxer"</string>
+    <string name="download_title" msgid="3353228219772092586">"Transferència de fitxers"</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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Àudio per Bluetooth connectat"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Àudio per Bluetooth desconnectat"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Àudio per Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"No es poden transferir fitxers més grans de 4 GB"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 5318747..d6020cf 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth Audio – připojeno"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth Audio – odpojeno"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Soubory větší než 4 GB nelze přenést"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 8dae389..7de8ec4 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-lyden blev tilsluttet"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-lyden blev afbrudt"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-lyd"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"File, der er større end 4 GB, kan ikke overføres"</string>
 </resources>
diff --git a/res/values-da/strings_pbap.xml b/res/values-da/strings_pbap.xml
index ae570c6..4837260 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">"Indtast sessionsnøgle til %1$s"</string>
+    <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"Angiv 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">"Indtast sessionsnøgle til %1$s"</string>
+    <string name="auth_notif_message" msgid="6667218116427605038">"Angiv 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 kontaktpersoner via Bluetooth"</string>
+    <string name="pbap_notification_group" msgid="8487669554703627168">"Deling af kontakter via Bluetooth"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index f882010..c8b882e 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -34,7 +34,7 @@
     <string name="incoming_file_confirm_content" msgid="2752605552743148036">"Eingehende Datei annehmen?"</string>
     <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Ablehnen"</string>
     <string name="incoming_file_confirm_ok" msgid="281462442932231475">"Akzeptieren"</string>
-    <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
+    <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Ok"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Die Zeit zum Empfang der eingehenden Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\" ist abgelaufen."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Eingehende Datei"</string>
     <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> kann jetzt <xliff:g id="FILE">%2$s</xliff:g> senden."</string>
@@ -59,19 +59,19 @@
     <string name="download_fail_line1" msgid="3846450148862894552">"Datei nicht empfangen"</string>
     <string name="download_fail_line2" msgid="8950394574689971071">"Datei: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="download_fail_line3" msgid="3451040656154861722">"Grund: <xliff:g id="REASON">%1$s</xliff:g>"</string>
-    <string name="download_fail_ok" msgid="1521733664438320300">"OK"</string>
+    <string name="download_fail_ok" msgid="1521733664438320300">"Ok"</string>
     <string name="download_succ_line5" msgid="4509944688281573595">"Datei empfangen"</string>
     <string name="download_succ_ok" msgid="7053688246357050216">"Öffnen"</string>
     <string name="upload_line1" msgid="2055952074059709052">"An: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="upload_line3" msgid="4920689672457037437">"Dateityp: <xliff:g id="TYPE">%1$s</xliff:g> (<xliff:g id="SIZE">%2$s</xliff:g>)"</string>
     <string name="upload_line5" msgid="7759322537674229752">"Datei wird gesendet..."</string>
     <string name="upload_succ_line5" msgid="5687317197463383601">"Die Datei wurde gesendet."</string>
-    <string name="upload_succ_ok" msgid="7705428476405478828">"OK"</string>
+    <string name="upload_succ_ok" msgid="7705428476405478828">"Ok"</string>
     <string name="upload_fail_line1" msgid="7899394672421491701">"Die Datei wurde nicht an \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" gesendet."</string>
     <string name="upload_fail_line1_2" msgid="2108129204050841798">"Datei: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="upload_fail_ok" msgid="5807702461606714296">"Wiederholen"</string>
     <string name="upload_fail_cancel" msgid="9118496285835687125">"Schließen"</string>
-    <string name="bt_error_btn_ok" msgid="5965151173011534240">"OK"</string>
+    <string name="bt_error_btn_ok" msgid="5965151173011534240">"Ok"</string>
     <string name="unknown_file" msgid="6092727753965095366">"Unbekannte Datei"</string>
     <string name="unknown_file_desc" msgid="480434281415453287">"Dieser Dateityp kann von keiner App verarbeitet werden. \n"</string>
     <string name="not_exist_file" msgid="3489434189599716133">"Keine Datei"</string>
@@ -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. Versuche es später erneut."</string>
+    <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Es werden zurzeit zu viele Anfragen verarbeitet. Bitte versuche es später noch einmal."</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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-Audio verbunden"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-Audio-Verbindung aufgehoben"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Dateien mit mehr als 4 GB können nicht übertragen werden"</string>
 </resources>
diff --git a/res/values-de/test_strings.xml b/res/values-de/test_strings.xml
index 0ce94ff..3e07229 100644
--- a/res/values-de/test_strings.xml
+++ b/res/values-de/test_strings.xml
@@ -6,7 +6,7 @@
     <string name="update_record" msgid="2480425402384910635">"Aufnahme bestätigen"</string>
     <string name="ack_record" msgid="6716152390978472184">"Aufnahme bestätigen"</string>
     <string name="deleteAll_record" msgid="4383349788485210582">"Gesamte Aufnahme löschen"</string>
-    <string name="ok_button" msgid="6519033415223065454">"OK"</string>
+    <string name="ok_button" msgid="6519033415223065454">"Ok"</string>
     <string name="delete_record" msgid="4645040331967533724">"Aufnahme löschen"</string>
     <string name="start_server" msgid="9034821924409165795">"TCP-Server starten"</string>
     <string name="notify_server" msgid="4369106744022969655">"TCP-Server benachrichtigen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 7f9073b..71c10d2 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -132,4 +132,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Δεν είναι δυνατή η μεταφορά αρχείων που ξεπερνούν τα 4 GB"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index c92f8f7..ba19b9c 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio connected"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index f43c385..efd1453 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio connected"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index c92f8f7..ba19b9c 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio connected"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index c92f8f7..ba19b9c 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio connected"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
 </resources>
diff --git a/res/values-en-rXC/config.xml b/res/values-en-rXC/config.xml
index c77a0a4..5653f1d 100644
--- a/res/values-en-rXC/config.xml
+++ b/res/values-en-rXC/config.xml
@@ -15,5 +15,5 @@
 
 <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>
+    <string name="pairing_ui_package" msgid="6399948348712579121">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎com.android.settings‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 443e0a0..47646b1 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -16,120 +16,121 @@
 
 <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">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎Access download manager.‎‏‎‎‏‎"</string>
-    <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎Allows the app to access the BluetoothShare manager and use it to transfer files.‎‏‎‎‏‎"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎Whitelist bluetooth device access.‎‏‎‎‏‎"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎Allows the app to temporarily whitelist a Bluetooth device, allowing that device to send files to this device without user confirmation.‎‏‎‎‏‎"</string>
-    <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="permlab_bluetoothShareManager" msgid="311492132450338925">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎Access download manager.‎‏‎‎‏‎"</string>
+    <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎Allows the app to access the BluetoothShare manager and use it to transfer files.‎‏‎‎‏‎"</string>
+    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎Whitelist bluetooth device access.‎‏‎‎‏‎"</string>
+    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎Allows the app to temporarily whitelist a Bluetooth device, allowing that device to send files to this device without user confirmation.‎‏‎‎‏‎"</string>
+    <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="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>
-    <string name="bt_enable_cancel" msgid="1988832367505151727">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‏‏‎Cancel‎‏‎‎‏‎"</string>
-    <string name="bt_enable_ok" msgid="3432462749994538265">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎‏‎‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‏‏‎‎‏‎Turn on‎‏‎‎‏‎"</string>
-    <string name="incoming_file_confirm_title" msgid="8139874248612182627">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎File transfer‎‏‎‎‏‎"</string>
-    <string name="incoming_file_confirm_content" msgid="2752605552743148036">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎Accept incoming file?‎‏‎‎‏‎"</string>
-    <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎Decline‎‏‎‎‏‎"</string>
-    <string name="incoming_file_confirm_ok" msgid="281462442932231475">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎Accept‎‏‎‎‏‎"</string>
-    <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎OK‎‏‎‎‏‎"</string>
-    <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‎There was a timeout while accepting an incoming file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‎Incoming file‎‏‎‎‏‎"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is ready to send ‎‏‎‎‏‏‎<xliff:g id="FILE">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="notification_receiving" msgid="4674648179652543984">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎Bluetooth share: Receiving ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="notification_received" msgid="3324588019186687985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎Bluetooth share: Received ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="notification_received_fail" msgid="3619350997285714746">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎Bluetooth share: File ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ not received‎‏‎‎‏‎"</string>
-    <string name="notification_sending" msgid="3035748958534983833">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎Bluetooth share: Sending ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="notification_sent" msgid="9218710861333027778">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎‎‎‎‏‎‎Bluetooth share: Sent ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="notification_sent_complete" msgid="302943281067557969">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎100% complete‎‏‎‎‏‎"</string>
-    <string name="notification_sent_fail" msgid="6696082233774569445">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎Bluetooth share: File ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ not sent‎‏‎‎‏‎"</string>
-    <string name="download_title" msgid="3353228219772092586">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎File transfer‎‏‎‎‏‎"</string>
-    <string name="download_line1" msgid="4926604799202134144">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‎‎From: \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="download_line2" msgid="5876973543019417712">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‎File: ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="download_line3" msgid="4384821622908676061">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎File size: ‎‏‎‎‏‏‎<xliff:g id="SIZE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</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>
+    <string name="bt_enable_cancel" msgid="1988832367505151727">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‏‏‎Cancel‎‏‎‎‏‎"</string>
+    <string name="bt_enable_ok" msgid="3432462749994538265">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎‏‎‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‏‏‎‎‏‎Turn on‎‏‎‎‏‎"</string>
+    <string name="incoming_file_confirm_title" msgid="8139874248612182627">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎File transfer‎‏‎‎‏‎"</string>
+    <string name="incoming_file_confirm_content" msgid="2752605552743148036">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎Accept incoming file?‎‏‎‎‏‎"</string>
+    <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎Decline‎‏‎‎‏‎"</string>
+    <string name="incoming_file_confirm_ok" msgid="281462442932231475">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎Accept‎‏‎‎‏‎"</string>
+    <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎OK‎‏‎‎‏‎"</string>
+    <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‎There was a timeout while accepting an incoming file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‎Incoming file‎‏‎‎‏‎"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is ready to send ‎‏‎‎‏‏‎<xliff:g id="FILE">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="notification_receiving" msgid="4674648179652543984">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎Bluetooth share: Receiving ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="notification_received" msgid="3324588019186687985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎Bluetooth share: Received ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="notification_received_fail" msgid="3619350997285714746">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎Bluetooth share: File ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ not received‎‏‎‎‏‎"</string>
+    <string name="notification_sending" msgid="3035748958534983833">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎Bluetooth share: Sending ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="notification_sent" msgid="9218710861333027778">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎‎‎‎‏‎‎Bluetooth share: Sent ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="notification_sent_complete" msgid="302943281067557969">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎100% complete‎‏‎‎‏‎"</string>
+    <string name="notification_sent_fail" msgid="6696082233774569445">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎Bluetooth share: File ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ not sent‎‏‎‎‏‎"</string>
+    <string name="download_title" msgid="3353228219772092586">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎File transfer‎‏‎‎‏‎"</string>
+    <string name="download_line1" msgid="4926604799202134144">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‎‎‎From: \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="download_line2" msgid="5876973543019417712">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‎File: ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="download_line3" msgid="4384821622908676061">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎File size: ‎‏‎‎‏‏‎<xliff:g id="SIZE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="download_line4" msgid="8535996869722666525"></string>
-    <string name="download_line5" msgid="3069560415845295386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‏‎‎‎‏‏‎‏‎‎Receiving file…‎‏‎‎‏‎"</string>
-    <string name="download_cancel" msgid="9177305996747500768">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‎‎‎Stop‎‏‎‎‏‎"</string>
-    <string name="download_ok" msgid="5000360731674466039">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎Hide‎‏‎‎‏‎"</string>
-    <string name="incoming_line1" msgid="2127419875681087545">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‎From‎‏‎‎‏‎"</string>
-    <string name="incoming_line2" msgid="3348994249285315873">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‎‏‎Filename‎‏‎‎‏‎"</string>
-    <string name="incoming_line3" msgid="7954237069667474024">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎Size‎‏‎‎‏‎"</string>
-    <string name="download_fail_line1" msgid="3846450148862894552">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎File not received‎‏‎‎‏‎"</string>
-    <string name="download_fail_line2" msgid="8950394574689971071">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‏‏‎File: ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="download_fail_line3" msgid="3451040656154861722">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‎‎Reason: ‎‏‎‎‏‏‎<xliff:g id="REASON">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="download_fail_ok" msgid="1521733664438320300">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎OK‎‏‎‎‏‎"</string>
-    <string name="download_succ_line5" msgid="4509944688281573595">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‏‎File received‎‏‎‎‏‎"</string>
-    <string name="download_succ_ok" msgid="7053688246357050216">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎Open‎‏‎‎‏‎"</string>
-    <string name="upload_line1" msgid="2055952074059709052">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎To: \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="upload_line3" msgid="4920689672457037437">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎File type: ‎‏‎‎‏‏‎<xliff:g id="TYPE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ (‎‏‎‎‏‏‎<xliff:g id="SIZE">%2$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
-    <string name="upload_line5" msgid="7759322537674229752">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎Sending file…‎‏‎‎‏‎"</string>
-    <string name="upload_succ_line5" msgid="5687317197463383601">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‎File sent‎‏‎‎‏‎"</string>
-    <string name="upload_succ_ok" msgid="7705428476405478828">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎OK‎‏‎‎‏‎"</string>
-    <string name="upload_fail_line1" msgid="7899394672421491701">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎The file wasn\'t sent to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\".‎‏‎‎‏‎"</string>
-    <string name="upload_fail_line1_2" msgid="2108129204050841798">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‏‎‎File: ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="upload_fail_ok" msgid="5807702461606714296">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‏‎‎‎‎Try again‎‏‎‎‏‎"</string>
-    <string name="upload_fail_cancel" msgid="9118496285835687125">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎Close‎‏‎‎‏‎"</string>
-    <string name="bt_error_btn_ok" msgid="5965151173011534240">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎OK‎‏‎‎‏‎"</string>
-    <string name="unknown_file" msgid="6092727753965095366">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‏‏‎‎Unknown file‎‏‎‎‏‎"</string>
-    <string name="unknown_file_desc" msgid="480434281415453287">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‏‎There\'s no app to handle this type of file. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="not_exist_file" msgid="3489434189599716133">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎No file‎‏‎‎‏‎"</string>
-    <string name="not_exist_file_desc" msgid="4059531573790529229">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎The file doesn\'t exist. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="enabling_progress_title" msgid="436157952334723406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‎Please wait…‎‏‎‎‏‎"</string>
-    <string name="enabling_progress_content" msgid="4601542238119927904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‎Turning on Bluetooth…‎‏‎‎‏‎"</string>
-    <string name="bt_toast_1" msgid="972182708034353383">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‏‏‏‎The file will be received. Check progress in the Notifications panel.‎‏‎‎‏‎"</string>
-    <string name="bt_toast_2" msgid="8602553334099066582">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎The file can\'t be received.‎‏‎‎‏‎"</string>
-    <string name="bt_toast_3" msgid="6707884165086862518">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎Stopped receiving file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="bt_toast_4" msgid="4678812947604395649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎Sending file to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="bt_toast_5" msgid="2846870992823019494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎Sending ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ files to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%2$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="bt_toast_6" msgid="1855266596936622458">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‎‏‎‎Stopped sending file to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‎‎There isn\'t enough space in USB storage to save the file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‏‎‏‏‏‎‎There isn\'t enough space on the SD card to save the file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="bt_sm_2_2" msgid="2965243265852680543">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎Space needed: ‎‏‎‎‏‏‎<xliff:g id="SIZE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="ErrorTooManyRequests" msgid="8578277541472944529">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎Too many requests are being processed. Try again later.‎‏‎‎‏‎"</string>
-    <string name="status_pending" msgid="2503691772030877944">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎File transfer not started yet.‎‏‎‎‏‎"</string>
-    <string name="status_running" msgid="6562808920311008696">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‏‏‎‎‎‎File transfer is ongoing.‎‏‎‎‏‎"</string>
-    <string name="status_success" msgid="239573225847565868">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎File transfer completed successfully.‎‏‎‎‏‎"</string>
-    <string name="status_not_accept" msgid="1695082417193780738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‎Content isn\'t supported.‎‏‎‎‏‎"</string>
-    <string name="status_forbidden" msgid="613956401054050725">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‏‎Transfer forbidden by target device.‎‏‎‎‏‎"</string>
-    <string name="status_canceled" msgid="6664490318773098285">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‏‎Transfer canceled by user.‎‏‎‎‏‎"</string>
-    <string name="status_file_error" msgid="3671917770630165299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎Storage issue.‎‏‎‎‏‎"</string>
-    <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎No USB storage.‎‏‎‎‏‎"</string>
-    <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‏‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎No SD card. Insert an SD card to save transferred files.‎‏‎‎‏‎"</string>
-    <string name="status_connection_error" msgid="947681831523219891">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‎Connection unsuccessful.‎‏‎‎‏‎"</string>
-    <string name="status_protocol_error" msgid="3245444473429269539">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‏‎Request can\'t be handled correctly.‎‏‎‎‏‎"</string>
-    <string name="status_unknown_error" msgid="8156660554237824912">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎Unknown error.‎‏‎‎‏‎"</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎Bluetooth received‎‏‎‎‏‎"</string>
-    <string name="opp_notification_group" msgid="3486303082135789982">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎‏‏‎‎‏‏‏‏‎‎Bluetooth Share‎‏‎‎‏‎"</string>
-    <string name="download_success" msgid="7036160438766730871">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="FILE_SIZE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ Received complete.‎‏‎‎‏‎"</string>
-    <string name="upload_success" msgid="4014469387779648949">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="FILE_SIZE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ Sent complete.‎‏‎‎‏‎"</string>
-    <string name="inbound_history_title" msgid="6940914942271327563">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‎Inbound transfers‎‏‎‎‏‎"</string>
-    <string name="outbound_history_title" msgid="4279418703178140526">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‎Outbound transfers‎‏‎‎‏‎"</string>
-    <string name="no_transfers" msgid="3482965619151865672">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‎‎Transfer history is empty.‎‏‎‎‏‎"</string>
-    <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎All items will be cleared from the list.‎‏‎‎‏‎"</string>
-    <string name="outbound_noti_title" msgid="8051906709452260849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎Bluetooth share: Sent files‎‏‎‎‏‎"</string>
-    <string name="inbound_noti_title" msgid="4143352641953027595">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎Bluetooth share: Received files‎‏‎‎‏‎"</string>
+    <string name="download_line5" msgid="3069560415845295386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‏‎‎‎‏‏‎‏‎‎Receiving file…‎‏‎‎‏‎"</string>
+    <string name="download_cancel" msgid="9177305996747500768">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‎‎‎Stop‎‏‎‎‏‎"</string>
+    <string name="download_ok" msgid="5000360731674466039">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎Hide‎‏‎‎‏‎"</string>
+    <string name="incoming_line1" msgid="2127419875681087545">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‎From‎‏‎‎‏‎"</string>
+    <string name="incoming_line2" msgid="3348994249285315873">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‎‏‎Filename‎‏‎‎‏‎"</string>
+    <string name="incoming_line3" msgid="7954237069667474024">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‎‎‏‎‏‏‎‏‎‎‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎Size‎‏‎‎‏‎"</string>
+    <string name="download_fail_line1" msgid="3846450148862894552">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎File not received‎‏‎‎‏‎"</string>
+    <string name="download_fail_line2" msgid="8950394574689971071">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‏‏‎File: ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="download_fail_line3" msgid="3451040656154861722">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‎‎Reason: ‎‏‎‎‏‏‎<xliff:g id="REASON">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="download_fail_ok" msgid="1521733664438320300">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎OK‎‏‎‎‏‎"</string>
+    <string name="download_succ_line5" msgid="4509944688281573595">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‏‎File received‎‏‎‎‏‎"</string>
+    <string name="download_succ_ok" msgid="7053688246357050216">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎Open‎‏‎‎‏‎"</string>
+    <string name="upload_line1" msgid="2055952074059709052">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎To: \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="upload_line3" msgid="4920689672457037437">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎File type: ‎‏‎‎‏‏‎<xliff:g id="TYPE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ (‎‏‎‎‏‏‎<xliff:g id="SIZE">%2$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
+    <string name="upload_line5" msgid="7759322537674229752">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎Sending file…‎‏‎‎‏‎"</string>
+    <string name="upload_succ_line5" msgid="5687317197463383601">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‎File sent‎‏‎‎‏‎"</string>
+    <string name="upload_succ_ok" msgid="7705428476405478828">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‎OK‎‏‎‎‏‎"</string>
+    <string name="upload_fail_line1" msgid="7899394672421491701">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎The file wasn\'t sent to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\".‎‏‎‎‏‎"</string>
+    <string name="upload_fail_line1_2" msgid="2108129204050841798">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‏‎‎File: ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="upload_fail_ok" msgid="5807702461606714296">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‏‎‎‎‎Try again‎‏‎‎‏‎"</string>
+    <string name="upload_fail_cancel" msgid="9118496285835687125">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎Close‎‏‎‎‏‎"</string>
+    <string name="bt_error_btn_ok" msgid="5965151173011534240">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎OK‎‏‎‎‏‎"</string>
+    <string name="unknown_file" msgid="6092727753965095366">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‏‏‎‎Unknown file‎‏‎‎‏‎"</string>
+    <string name="unknown_file_desc" msgid="480434281415453287">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‏‎There\'s no app to handle this type of file. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="not_exist_file" msgid="3489434189599716133">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎No file‎‏‎‎‏‎"</string>
+    <string name="not_exist_file_desc" msgid="4059531573790529229">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎The file doesn\'t exist. ‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="enabling_progress_title" msgid="436157952334723406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‎Please wait…‎‏‎‎‏‎"</string>
+    <string name="enabling_progress_content" msgid="4601542238119927904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‎Turning on Bluetooth…‎‏‎‎‏‎"</string>
+    <string name="bt_toast_1" msgid="972182708034353383">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‏‏‏‎The file will be received. Check progress in the Notifications panel.‎‏‎‎‏‎"</string>
+    <string name="bt_toast_2" msgid="8602553334099066582">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎The file can\'t be received.‎‏‎‎‏‎"</string>
+    <string name="bt_toast_3" msgid="6707884165086862518">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎Stopped receiving file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="bt_toast_4" msgid="4678812947604395649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎Sending file to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="bt_toast_5" msgid="2846870992823019494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎Sending ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ files to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%2$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="bt_toast_6" msgid="1855266596936622458">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‎‏‎‎Stopped sending file to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="bt_sm_2_1" product="nosdcard" msgid="352165168004521000">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‎‎There isn\'t enough space in USB storage to save the file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="bt_sm_2_1" product="default" msgid="1989018443456803630">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‏‎‏‏‏‎‎There isn\'t enough space on the SD card to save the file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="bt_sm_2_2" msgid="2965243265852680543">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎Space needed: ‎‏‎‎‏‏‎<xliff:g id="SIZE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="ErrorTooManyRequests" msgid="8578277541472944529">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎Too many requests are being processed. Try again later.‎‏‎‎‏‎"</string>
+    <string name="status_pending" msgid="2503691772030877944">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎File transfer not started yet.‎‏‎‎‏‎"</string>
+    <string name="status_running" msgid="6562808920311008696">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‏‏‎‎‎‎File transfer is ongoing.‎‏‎‎‏‎"</string>
+    <string name="status_success" msgid="239573225847565868">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎File transfer completed successfully.‎‏‎‎‏‎"</string>
+    <string name="status_not_accept" msgid="1695082417193780738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‎Content isn\'t supported.‎‏‎‎‏‎"</string>
+    <string name="status_forbidden" msgid="613956401054050725">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‏‎Transfer forbidden by target device.‎‏‎‎‏‎"</string>
+    <string name="status_canceled" msgid="6664490318773098285">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‏‎Transfer canceled by user.‎‏‎‎‏‎"</string>
+    <string name="status_file_error" msgid="3671917770630165299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎Storage issue.‎‏‎‎‏‎"</string>
+    <string name="status_no_sd_card" product="nosdcard" msgid="1112125377088421469">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎No USB storage.‎‏‎‎‏‎"</string>
+    <string name="status_no_sd_card" product="default" msgid="5760944071743325592">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‏‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎No SD card. Insert an SD card to save transferred files.‎‏‎‎‏‎"</string>
+    <string name="status_connection_error" msgid="947681831523219891">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‎Connection unsuccessful.‎‏‎‎‏‎"</string>
+    <string name="status_protocol_error" msgid="3245444473429269539">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‏‎Request can\'t be handled correctly.‎‏‎‎‏‎"</string>
+    <string name="status_unknown_error" msgid="8156660554237824912">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎Unknown error.‎‏‎‎‏‎"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎Bluetooth received‎‏‎‎‏‎"</string>
+    <string name="opp_notification_group" msgid="3486303082135789982">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎‏‏‎‎‏‏‏‏‎‎Bluetooth Share‎‏‎‎‏‎"</string>
+    <string name="download_success" msgid="7036160438766730871">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="FILE_SIZE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ Received complete.‎‏‎‎‏‎"</string>
+    <string name="upload_success" msgid="4014469387779648949">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="FILE_SIZE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ Sent complete.‎‏‎‎‏‎"</string>
+    <string name="inbound_history_title" msgid="6940914942271327563">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‎Inbound transfers‎‏‎‎‏‎"</string>
+    <string name="outbound_history_title" msgid="4279418703178140526">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‎‎Outbound transfers‎‏‎‎‏‎"</string>
+    <string name="no_transfers" msgid="3482965619151865672">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‎‎Transfer history is empty.‎‏‎‎‏‎"</string>
+    <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎All items will be cleared from the list.‎‏‎‎‏‎"</string>
+    <string name="outbound_noti_title" msgid="8051906709452260849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎Bluetooth share: Sent files‎‏‎‎‏‎"</string>
+    <string name="inbound_noti_title" msgid="4143352641953027595">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎Bluetooth share: Received files‎‏‎‎‏‎"</string>
     <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g>‎‏‎‎‏‏‏‎ unsuccessful.‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g>‎‏‎‎‏‏‏‎ unsuccessful.‎‏‎‎‏‎</item>
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g>‎‏‎‎‏‏‏‎ unsuccessful.‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g>‎‏‎‎‏‏‏‎ unsuccessful.‎‏‎‎‏‎</item>
     </plurals>
     <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
-      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g>‎‏‎‎‏‏‏‎ successful, %2$s‎‏‎‎‏‎</item>
-      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g>‎‏‎‎‏‏‏‎ successful, %2$s‎‏‎‎‏‎</item>
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g>‎‏‎‎‏‏‏‎ successful, %2$s‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g>‎‏‎‎‏‏‏‎ successful, %2$s‎‏‎‎‏‎</item>
     </plurals>
-    <string name="transfer_menu_clear_all" msgid="790017462957873132">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‎Clear list‎‏‎‎‏‎"</string>
-    <string name="transfer_menu_open" msgid="3368984869083107200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‎‎‎‎‎‎‎Open‎‏‎‎‏‎"</string>
-    <string name="transfer_menu_clear" msgid="5854038118831427492">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎‎Clear from list‎‏‎‎‏‎"</string>
-    <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎Clear‎‏‎‎‏‎"</string>
-    <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‎Save‎‏‎‎‏‎"</string>
-    <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎Cancel‎‏‎‎‏‎"</string>
-    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎Select the accounts you want to share through Bluetooth. You still have to accept any access to the accounts when connecting.‎‏‎‎‏‎"</string>
-    <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎Slots left:‎‏‎‎‏‎"</string>
-    <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎Application Icon‎‏‎‎‏‎"</string>
-    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎Bluetooth Message Sharing Settings‎‏‎‎‏‎"</string>
-    <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‎‎‏‏‎Cannot select account. 0 slots left‎‏‎‎‏‎"</string>
-    <string name="bluetooth_connected" msgid="6718623220072656906">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‎Bluetooth audio connected‎‏‎‎‏‎"</string>
-    <string name="bluetooth_disconnected" msgid="3318303728981478873">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎Bluetooth audio disconnected‎‏‎‎‏‎"</string>
-    <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎Bluetooth Audio‎‏‎‎‏‎"</string>
+    <string name="transfer_menu_clear_all" msgid="790017462957873132">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‎Clear list‎‏‎‎‏‎"</string>
+    <string name="transfer_menu_open" msgid="3368984869083107200">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‎‎‎‎‎‎‎Open‎‏‎‎‏‎"</string>
+    <string name="transfer_menu_clear" msgid="5854038118831427492">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎‎Clear from list‎‏‎‎‏‎"</string>
+    <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‎‎‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎Clear‎‏‎‎‏‎"</string>
+    <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‎Save‎‏‎‎‏‎"</string>
+    <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎Cancel‎‏‎‎‏‎"</string>
+    <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎Select the accounts you want to share through Bluetooth. You still have to accept any access to the accounts when connecting.‎‏‎‎‏‎"</string>
+    <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎Slots left:‎‏‎‎‏‎"</string>
+    <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎Application Icon‎‏‎‎‏‎"</string>
+    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎Bluetooth Message Sharing Settings‎‏‎‎‏‎"</string>
+    <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‎‎‏‏‎Cannot select account. 0 slots left‎‏‎‎‏‎"</string>
+    <string name="bluetooth_connected" msgid="6718623220072656906">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‎Bluetooth audio connected‎‏‎‎‏‎"</string>
+    <string name="bluetooth_disconnected" msgid="3318303728981478873">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎Bluetooth audio disconnected‎‏‎‎‏‎"</string>
+    <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎Bluetooth Audio‎‏‎‎‏‎"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎Files bigger than 4GB cannot be transferred‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-en-rXC/strings_pbap.xml b/res/values-en-rXC/strings_pbap.xml
index fee6dbd..e820513 100644
--- a/res/values-en-rXC/strings_pbap.xml
+++ b/res/values-en-rXC/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">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‎Type session key for %1$s‎‏‎‎‏‎"</string>
-    <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‏‎Bluetooth session key required‎‏‎‎‏‎"</string>
-    <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‎There was time out to accept connection with %1$s‎‏‎‎‏‎"</string>
-    <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎There was time out to input session key with %1$s‎‏‎‎‏‎"</string>
-    <string name="auth_notif_ticker" msgid="1575825798053163744">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎Obex authentication request‎‏‎‎‏‎"</string>
-    <string name="auth_notif_title" msgid="7599854855681573258">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‎Session Key‎‏‎‎‏‎"</string>
-    <string name="auth_notif_message" msgid="6667218116427605038">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎Type session key for %1$s‎‏‎‎‏‎"</string>
-    <string name="defaultname" msgid="4821590500649090078">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎Carkit‎‏‎‎‏‎"</string>
-    <string name="unknownName" msgid="2841414754740600042">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎Unknown name‎‏‎‎‏‎"</string>
-    <string name="localPhoneName" msgid="2349001318925409159">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‏‎My name‎‏‎‎‏‎"</string>
-    <string name="defaultnumber" msgid="8520116145890867338">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎000000‎‏‎‎‏‎"</string>
-    <string name="pbap_notification_group" msgid="8487669554703627168">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎Bluetooth Contact share‎‏‎‎‏‎"</string>
+    <string name="pbap_session_key_dialog_title" msgid="3580996574333882561">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‎Type session key for %1$s‎‏‎‎‏‎"</string>
+    <string name="pbap_session_key_dialog_header" msgid="2772472422782758981">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‏‎Bluetooth session key required‎‏‎‎‏‎"</string>
+    <string name="pbap_acceptance_timeout_message" msgid="1107401415099814293">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‎There was time out to accept connection with %1$s‎‏‎‎‏‎"</string>
+    <string name="pbap_authentication_timeout_message" msgid="4166979525521902687">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎There was time out to input session key with %1$s‎‏‎‎‏‎"</string>
+    <string name="auth_notif_ticker" msgid="1575825798053163744">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎Obex authentication request‎‏‎‎‏‎"</string>
+    <string name="auth_notif_title" msgid="7599854855681573258">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‎Session Key‎‏‎‎‏‎"</string>
+    <string name="auth_notif_message" msgid="6667218116427605038">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎Type session key for %1$s‎‏‎‎‏‎"</string>
+    <string name="defaultname" msgid="4821590500649090078">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎Carkit‎‏‎‎‏‎"</string>
+    <string name="unknownName" msgid="2841414754740600042">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎Unknown name‎‏‎‎‏‎"</string>
+    <string name="localPhoneName" msgid="2349001318925409159">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‏‎My name‎‏‎‎‏‎"</string>
+    <string name="defaultnumber" msgid="8520116145890867338">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎000000‎‏‎‎‏‎"</string>
+    <string name="pbap_notification_group" msgid="8487669554703627168">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎Bluetooth Contact share‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-en-rXC/strings_pbap_client.xml b/res/values-en-rXC/strings_pbap_client.xml
index 5d7540f..6a0780a 100644
--- a/res/values-en-rXC/strings_pbap_client.xml
+++ b/res/values-en-rXC/strings_pbap_client.xml
@@ -1,5 +1,5 @@
 <?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>
+    <string name="pbap_account_type" msgid="6257077123906049322">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‎com.android.bluetooth.pbapsink‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-en-rXC/strings_sap.xml b/res/values-en-rXC/strings_sap.xml
index 0b0524f..c838e6f 100644
--- a/res/values-en-rXC/strings_sap.xml
+++ b/res/values-en-rXC/strings_sap.xml
@@ -1,10 +1,10 @@
 <?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">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‎Bluetooth SIM access‎‏‎‎‏‎"</string>
-    <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‏‎‏‏‏‏‎‏‎‏‏‎Bluetooth SIM Access‎‏‎‎‏‎"</string>
-    <string name="bluetooth_sap_notif_message" msgid="7138657801087500690">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‏‎‎Request client to disconnect?‎‏‎‎‏‎"</string>
-    <string name="bluetooth_sap_notif_disconnecting" msgid="819150843490233288">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎Waiting for client to disconnect‎‏‎‎‏‎"</string>
-    <string name="bluetooth_sap_notif_disconnect_button" msgid="3678476872583356919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎Disconnect‎‏‎‎‏‎"</string>
-    <string name="bluetooth_sap_notif_force_disconnect_button" msgid="8144086340185532030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎Force disconnect‎‏‎‎‏‎"</string>
+    <string name="bluetooth_sap_notif_title" msgid="6877860822993195074">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‎Bluetooth SIM access‎‏‎‎‏‎"</string>
+    <string name="bluetooth_sap_notif_ticker" msgid="6807778527893726699">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‎‎‏‎‎‏‎‏‏‏‏‎‏‎‏‏‎Bluetooth SIM Access‎‏‎‎‏‎"</string>
+    <string name="bluetooth_sap_notif_message" msgid="7138657801087500690">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‏‎‎Request client to disconnect?‎‏‎‎‏‎"</string>
+    <string name="bluetooth_sap_notif_disconnecting" msgid="819150843490233288">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎Waiting for client to disconnect‎‏‎‎‏‎"</string>
+    <string name="bluetooth_sap_notif_disconnect_button" msgid="3678476872583356919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎Disconnect‎‏‎‎‏‎"</string>
+    <string name="bluetooth_sap_notif_force_disconnect_button" msgid="8144086340185532030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎Force disconnect‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-en-rXC/test_strings.xml b/res/values-en-rXC/test_strings.xml
index c0638ff..d1df1fc 100644
--- a/res/values-en-rXC/test_strings.xml
+++ b/res/values-en-rXC/test_strings.xml
@@ -1,13 +1,13 @@
 <?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">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎Bluetooth‎‏‎‎‏‎"</string>
-    <string name="insert_record" msgid="1450997173838378132">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‎Insert record‎‏‎‎‏‎"</string>
-    <string name="update_record" msgid="2480425402384910635">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎Confirm record‎‏‎‎‏‎"</string>
-    <string name="ack_record" msgid="6716152390978472184">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎‎‎‎Ack record‎‏‎‎‏‎"</string>
-    <string name="deleteAll_record" msgid="4383349788485210582">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‏‎‎Delete all record‎‏‎‎‏‎"</string>
-    <string name="ok_button" msgid="6519033415223065454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎OK‎‏‎‎‏‎"</string>
-    <string name="delete_record" msgid="4645040331967533724">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‎‎‎Delete record‎‏‎‎‏‎"</string>
-    <string name="start_server" msgid="9034821924409165795">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎Start TCP server‎‏‎‎‏‎"</string>
-    <string name="notify_server" msgid="4369106744022969655">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎Notify TCP server‎‏‎‎‏‎"</string>
+    <string name="app_name" msgid="6006644116867509664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎Bluetooth‎‏‎‎‏‎"</string>
+    <string name="insert_record" msgid="1450997173838378132">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‎Insert record‎‏‎‎‏‎"</string>
+    <string name="update_record" msgid="2480425402384910635">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎Confirm record‎‏‎‎‏‎"</string>
+    <string name="ack_record" msgid="6716152390978472184">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎‎‎‎Ack record‎‏‎‎‏‎"</string>
+    <string name="deleteAll_record" msgid="4383349788485210582">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‏‎‎Delete all record‎‏‎‎‏‎"</string>
+    <string name="ok_button" msgid="6519033415223065454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎OK‎‏‎‎‏‎"</string>
+    <string name="delete_record" msgid="4645040331967533724">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‎‎‎Delete record‎‏‎‎‏‎"</string>
+    <string name="start_server" msgid="9034821924409165795">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎Start TCP server‎‏‎‎‏‎"</string>
+    <string name="notify_server" msgid="4369106744022969655">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎Notify TCP server‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index e27ece8..1b142d4 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">"Recibidos por Bluetooth"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Recibido 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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth conectado"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth desconectado"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"No se pueden transferir los archivos de más de 4 GB"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index de5d171..6350a29 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Audio por Bluetooth conectado"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio por Bluetooth desconectado"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio por Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"No se pueden transferir archivos de más de 4 GB"</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 0c322b8..413a4e1 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">"Bluetooth vastu võetud"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetoothiga 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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetoothi heli on ühendatud"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetoothi heli ühendus on katkestatud"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetoothi heli"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Faile, mis on üle 4 GB, ei saa üle kanda"</string>
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 4089e71..76b215d 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -17,17 +17,17 @@
 <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">"Atzitu deskargen kudeatzailea."</string>
-    <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Bluetooth bidezko partekatzeen kudeatzailea atzitzea eta fitxategiak transferitzeko erabiltzea baimentzen die aplikazioei."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Sartu sarbidedunen zerrendan Bluetooth gailua."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetooth gailu bat aldi baterako sarbidedunen zerrendan sartzea baimentzen die aplikazioei, gailu honetara fitxategiak bidaltzeko baimena izan dezan, baina gailu honen erabiltzaileari berrespena eskatu beharrik gabe."</string>
-    <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
+    <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Bluetooth bidezko partekatzeen kudeatzailea atzitzea eta fitxategiak transferitzeko erabiltzeko baimena ematen die aplikazioei."</string>
+    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Sartu sarbidedunen zerrendan Bluetooth bidezko gailua."</string>
+    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetooth bidezko gailu bat aldi baterako sarbidedunen zerrendan sartzeko baimena ematen die aplikazioei, gailu honetara fitxategiak bidaltzeko baimena izan dezan, baina gailu honen erabiltzaileari berrespena eskatu beharrik gabe."</string>
+    <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth-a"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Gailu ezezaguna"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Ezezaguna"</string>
     <string name="airplane_error_title" msgid="2683839635115739939">"Hegaldi modua"</string>
-    <string name="airplane_error_msg" msgid="8698965595254137230">"Ezin duzu Bluetootha Hegaldi moduan erabili."</string>
+    <string name="airplane_error_msg" msgid="8698965595254137230">"Ezin duzu erabili Bluetooth-a Hegaldi moduan."</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
-    <string name="bt_enable_line1" msgid="7203551583048149">"Bluetooth-zerbitzuak erabiltzeko, Bluetootha aktibatu behar duzu."</string>
-    <string name="bt_enable_line2" msgid="4341936569415937994">"Bluetootha aktibatu nahi duzu?"</string>
+    <string name="bt_enable_line1" msgid="7203551583048149">"Bluetooth-zerbitzuak erabiltzeko, Bluetooth-a aktibatu behar duzu."</string>
+    <string name="bt_enable_line2" msgid="4341936569415937994">"Bluetooth-a aktibatu nahi duzu?"</string>
     <string name="bt_enable_cancel" msgid="1988832367505151727">"Utzi"</string>
     <string name="bt_enable_ok" msgid="3432462749994538265">"Aktibatu"</string>
     <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Fitxategi-transferentzia"</string>
@@ -77,7 +77,7 @@
     <string name="not_exist_file" msgid="3489434189599716133">"Ez dago fitxategirik"</string>
     <string name="not_exist_file_desc" msgid="4059531573790529229">"Ez dago horrelako fitxategirik. \n"</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"Itxaron…"</string>
-    <string name="enabling_progress_content" msgid="4601542238119927904">"Bluetootha aktibatzen…"</string>
+    <string name="enabling_progress_content" msgid="4601542238119927904">"Bluetooth-a aktibatzen…"</string>
     <string name="bt_toast_1" msgid="972182708034353383">"Fitxategia jasoko da. Egoera kontrolatzeko, joan Jakinarazpenen panelera."</string>
     <string name="bt_toast_2" msgid="8602553334099066582">"Ezin da fitxategia jaso."</string>
     <string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" igorlearen fitxategia jasotzeari utzi zaio"</string>
@@ -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 bidezko elementua jaso da"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth bidez jasotakoak"</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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Konektatu da Bluetooth bidezko audioa"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Deskonektatu da Bluetooth bidezko audioa"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth bidezko audioa"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Ezin dira transferitu 4 GB baino gehiagoko fitxategiak"</string>
 </resources>
diff --git a/res/values-eu/test_strings.xml b/res/values-eu/test_strings.xml
index 2497be5..e7236e7 100644
--- a/res/values-eu/test_strings.xml
+++ b/res/values-eu/test_strings.xml
@@ -1,7 +1,7 @@
 <?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">"Bluetooth konexioa"</string>
+    <string name="app_name" msgid="6006644116867509664">"Bluetooth-a"</string>
     <string name="insert_record" msgid="1450997173838378132">"Sartu erregistroa"</string>
     <string name="update_record" msgid="2480425402384910635">"Berretsi erregistroa"</string>
     <string name="ack_record" msgid="6716152390978472184">"ACK erregistroa"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 5784cdd..29f247a 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -132,4 +132,5 @@
     <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-fa/strings_pbap.xml b/res/values-fa/strings_pbap.xml
index 8282443..348156a 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-fi/strings.xml b/res/values-fi/strings.xml
index 773bf6b..1e56ca2 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-ääni liitetty"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-ääni katkaistu"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-ääni"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Yli 4 Gt:n kokoisia tiedostoja ei voi siirtää."</string>
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 90d4268..bcf5718 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth connecté"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth déconnecté"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Les fichiers dépassant 4 Go ne peuvent pas être transférés"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 57882fc..0555642 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Échec de la connexion."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Impossible de traiter la demande correctement."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Erreur inconnue."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Reçu par Bluetooth"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Reçus via Bluetooth"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Partage Bluetooth"</string>
     <string name="download_success" msgid="7036160438766730871">"Réception de <xliff:g id="FILE_SIZE">%1$s</xliff:g> terminée"</string>
     <string name="upload_success" msgid="4014469387779648949">"Envoi de <xliff:g id="FILE_SIZE">%1$s</xliff:g> terminé"</string>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth connecté"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth déconnecté"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Impossible de transférer les fichiers supérieurs à 4 Go"</string>
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index d3ec18a..6072260 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Conectouse o audio por Bluetooth"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Desconectouse o audio por Bluetooth"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio por Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Non se poden transferir ficheiros de máis de 4 GB"</string>
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index fc0a224..6860f15 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>
@@ -132,4 +132,5 @@
     <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-hi/strings.xml b/res/values-hi/strings.xml
index beae3c7..3da1fa7 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -43,7 +43,7 @@
     <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_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>
@@ -69,11 +69,11 @@
     <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_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="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>
@@ -87,7 +87,7 @@
     <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="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>
@@ -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="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="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>
@@ -124,7 +124,7 @@
     <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>
@@ -132,4 +132,5 @@
     <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">"4 जीबी से बड़ी फ़ाइलें ट्रांसफ़र नहीं की जा सकतीं"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 2376160..8c7a83b 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Neuspješno povezivanje."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Zahtjev nije moguće ispravno obraditi."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Nepoznata pogreška."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth primljen"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Primljeno Bluetoothom"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Dijeljenje Bluetoothom"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> primljeno u cijelosti."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Poslano u potpunosti."</string>
@@ -134,4 +134,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth Audio povezan"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Veza Bluetooth Audija prekinuta"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Datoteke veće od 4 GB ne mogu se prenijeti"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index e119aac..36b53d3 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audió csatlakoztatva"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audió leválasztva"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audió"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"A 4 GB-nál nagyobb fájlokat nem lehet átvinni"</string>
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 501f76e..a149d94 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -23,8 +23,8 @@
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</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">"Դուք չեք կարող օգտվել Bluetooth-ից Ինքնաթիռի ռեժիմում:"</string>
+    <string name="airplane_error_title" msgid="2683839635115739939">"Ավիառեժիմ"</string>
+    <string name="airplane_error_msg" msgid="8698965595254137230">"Դուք չեք կարող օգտվել Bluetooth-ից Ավիառեժիմում:"</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
     <string name="bt_enable_line1" msgid="7203551583048149">"Bluetooth ծառայություններից օգտվելու համար նախ պետք է միացնեք Bluetooth-ը:"</string>
     <string name="bt_enable_line2" msgid="4341936569415937994">"Միացնե՞լ Bluetooth-ը հիմա:"</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">"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>
@@ -132,4 +132,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 ԳԲ-ից մեծ ֆայլերը հնարավոր չէ փոխանցել"</string>
 </resources>
diff --git a/res/values-hy/test_strings.xml b/res/values-hy/test_strings.xml
index 147a0b4..33693e4 100644
--- a/res/values-hy/test_strings.xml
+++ b/res/values-hy/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-in/strings.xml b/res/values-in/strings.xml
index 84c9686..adf3b05 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth audio terhubung"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio terputus"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"File yang berukuran lebih dari 4GB tidak dapat ditransfer"</string>
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index f944207..44442c1 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-hljóð tengt"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-hljóð aftengt"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-hljóð"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Ekki er hægt að flytja skrár sem eru stærri en 4 GB"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 66226ad..52f9b3d 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth connesso"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth disconnesso"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Impossibile trasferire file con dimensioni superiori a 4 GB"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index ca48a38..602b60e 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>
@@ -136,4 +136,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"‏לא ניתן להעביר קבצים שגדולים מ-GB‏ 4"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 7dc2964..e4bff90 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -18,10 +18,10 @@
     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">"Bluetooth端末によるアクセスを許可します。"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetooth端末によるアクセスを一時的に許可して、ユーザーの確認を受けずにその端末からこの端末にファイルを送信することをアプリに許可します。"</string>
+    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetoothデバイスによるアクセスを許可します。"</string>
+    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetoothデバイスによるアクセスを一時的に許可して、ユーザーの確認を受けずにそのデバイスからこのデバイスにファイルを送信することをアプリに許可します。"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
-    <string name="unknown_device" msgid="9221903979877041009">"不明なモバイル端末"</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">"機内モードではBluetoothを使用できません。"</string>
@@ -92,7 +92,7 @@
     <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_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>
@@ -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>
@@ -132,4 +132,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB を超えるファイルは転送できません"</string>
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index fdf1677..bccc9b9 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -132,4 +132,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 გბაიტზე დიდი მოცულობის ფაილების გადატანა ვერ მოხერხდება"</string>
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 26b98d8..bb91d3e 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>
@@ -132,4 +132,5 @@
     <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 aудиосы"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Көлемі 4 ГБ-тан асатын файлдар тасымалданбайды"</string>
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index e6fb691..8be3840 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -132,4 +132,5 @@
     <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">"ឯកសារ​ដែល​មាន​ទំហំ​ធំ​ជាង 4 GB មិន​អាចផ្ទេរ​បាន​ទេ"</string>
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 9160f5d..00b4c1d 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -122,7 +122,7 @@
     <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_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>
@@ -132,4 +132,5 @@
     <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-ko/strings.xml b/res/values-ko/strings.xml
index 43b3aef..79405e5 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/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">"블루투스 수신함"</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>
@@ -132,4 +132,5 @@
     <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-ky/strings.xml b/res/values-ky/strings.xml
index aa101d5..b6065d9 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -97,16 +97,16 @@
     <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_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>
@@ -126,10 +126,11 @@
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Жокко чыгаруу"</string>
     <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташкан сайын каттоо эсептерине кирүү мүмкүнчүлүгүн ырастап турушуңуз керек."</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_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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4Гб чоң файлдарды өткөрүү мүмкүн эмес"</string>
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 60d6d1b..17ceec0 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -132,4 +132,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"ບໍ່ສາມາດໂອນຍ້າຍໄຟລ໌ທີ່ໃຫຍກວ່າ 4GB ໄດ້"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 743ca25..0849a5d 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"„Bluetooth“ garsas prijungtas"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"„Bluetooth“ garsas atjungtas"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"„Bluetooth“ garsas"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Negalima perkelti didesnių nei 4 GB failų"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index c80dda5..5e2f17c 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Izveidots savienojums ar Bluetooth audio"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Pārtraukts savienojums ar Bluetooth audio"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Nevar pārsūtīt failus, kas lielāki par 4 GB."</string>
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 9860c7c..7a2246f 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -93,14 +93,14 @@
     <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_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">"Нема СД картичка. Вметнете СД картичка да се зачуваат пренесените датотеки."</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">"Примен 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>
@@ -132,4 +132,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Не може да се пренесуваат датотеки поголеми од 4 GB"</string>
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 9bed8b8..c90308a 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/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">"ബ്ലൂടൂത്ത് ലഭിച്ചു"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"ബ്ലൂടൂത്തിലൂടെ ലഭിച്ചവ"</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>
@@ -130,4 +130,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB-യിൽ കൂടുതലുള്ള ഫയലുകൾ കൈമാറാനാവില്ല"</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index ac8f0dc..5bc336c 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -132,4 +132,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4ГБ-с дээш хэмжээтэй файлыг шилжүүлэх боломжгүй"</string>
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index fe4e32f..d5342de 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,17 +106,17 @@
     <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>
     <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_0">%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_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>
@@ -124,12 +124,13 @@
     <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>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ब्लूटूथ ऑडिओ"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB हून मोठ्या फायली ट्रान्सफर करता येणार नाहीत"</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index e45a257..8afc779 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Audio Bluetooth disambungkan"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth diputuskan sambungannya"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Fail lebih besar daripada 4GB tidak boleh dipindahkan"</string>
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 37279c0..9d70383 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>
@@ -132,4 +132,5 @@
     <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-nb/strings.xml b/res/values-nb/strings.xml
index 27adf49..12a24c4 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-lyd er tilkoblet"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-lyd er frakoblet"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-lyd"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Filer som er større enn 4 GB, kan ikke overføres"</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 84030ba..24c8a16 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>
@@ -132,4 +132,5 @@
     <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-ne/test_strings.xml b/res/values-ne/test_strings.xml
index ce2bc40..df5a45f 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 40bb6e2..39e300d 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 gereed 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 klaar 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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-audio gekoppeld"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-audio ontkoppeld"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Bestanden groter dan 4 GB kunnen niet worden overgedragen"</string>
 </resources>
diff --git a/res/values-or/config.xml b/res/values-or/config.xml
new file mode 100644
index 0000000..4f96544
--- /dev/null
+++ b/res/values-or/config.xml
@@ -0,0 +1,19 @@
+<?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
new file mode 100644
index 0000000..a927f7e
--- /dev/null
+++ b/res/values-or/strings.xml
@@ -0,0 +1,136 @@
+<?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
new file mode 100644
index 0000000..844a464
--- /dev/null
+++ b/res/values-or/strings_pbap.xml
@@ -0,0 +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">"%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
new file mode 100644
index 0000000..186b23a
--- /dev/null
+++ b/res/values-or/strings_pbap_client.xml
@@ -0,0 +1,5 @@
+<?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
new file mode 100644
index 0000000..aa190db
--- /dev/null
+++ b/res/values-or/strings_sap.xml
@@ -0,0 +1,10 @@
+<?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
new file mode 100644
index 0000000..4649a0a
--- /dev/null
+++ b/res/values-or/test_strings.xml
@@ -0,0 +1,13 @@
+<?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 73bcaa6..508dd3a 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -17,7 +17,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="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ਐਪ ਨੂੰ ਬਲੂਟੁੱਥShare ਪ੍ਰਬੰਧਕ ਤੱਕ ਪਹੁੰਚ ਅਤੇ ਫ਼ਾਈਲਾਂ ਟ੍ਰਾਂਸਫਰ ਕਰਨ ਲਈ ਇਸਦੀ ਵਰਤੋਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</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>
@@ -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">"ਬਲੂਟੁੱਥ ਰਾਹੀਂ ਪ੍ਰਾਪਤ"</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>
@@ -132,4 +132,5 @@
     <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-pl/strings.xml b/res/values-pl/strings.xml
index 47e3347..ee09650 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 na panelu powiadomień."</string>
+    <string name="bt_toast_1" msgid="972182708034353383">"Plik zostanie odebrany. Sprawdzaj postęp w 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>
@@ -136,4 +136,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Dźwięk Bluetooth podłączony"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Dźwięk Bluetooth odłączony"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Dźwięk Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Nie można przenieść plików przekraczających 4 GB"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 7dc2b31..ffc70a4 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Áudio Bluetooth ligado"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Áudio Bluetooth desligado"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Áudio Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Não é possível transferir os ficheiros com mais de 4 GB."</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 80e9184..a31eddb 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -100,7 +100,7 @@
     <string name="status_connection_error" msgid="947681831523219891">"Falha na conexão."</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"Não é possível tratar a solicitação corretamente."</string>
     <string name="status_unknown_error" msgid="8156660554237824912">"Erro desconhecido."</string>
-    <string name="btopp_live_folder" msgid="7967791481444474554">"Bluetooth recebido"</string>
+    <string name="btopp_live_folder" msgid="7967791481444474554">"Recebido por Bluetooth"</string>
     <string name="opp_notification_group" msgid="3486303082135789982">"Compartilhamento Bluetooth"</string>
     <string name="download_success" msgid="7036160438766730871">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Recebimento concluído."</string>
     <string name="upload_success" msgid="4014469387779648949">"<xliff:g id="FILE_SIZE">%1$s</xliff:g> Envio concluído."</string>
@@ -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ê deseja 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ê quer 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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Áudio Bluetooth conectado"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Áudio Bluetooth desconectado"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Áudio Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Não é possível transferir arquivos maiores que 4 GB"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 7faa761..b35a73a 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Audio prin Bluetooth conectat"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio prin Bluetooth deconectat"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio prin Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Fișierele mai mari de 4 GB nu pot fi transferate"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 85d810f..c3a2e08 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -136,4 +136,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Можно перенести только файлы размером до 4 ГБ."</string>
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index f700b3b..47a53f1 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -132,4 +132,5 @@
     <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-sk/strings.xml b/res/values-sk/strings.xml
index 2ccb74a..de2d261 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Rozhranie Bluetooth Audio je pripojené"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Rozhranie Bluetooth Audio je odpojené"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Súbory väčšie ako 4 GB sa nedajú preniesť"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index fc5f91a..a62bf24 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -136,4 +136,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Zvok prek Bluetootha je povezan"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Zvok prek Bluetootha ni povezan"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Zvok prek Bluetootha"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Datotek, večjih od 4 GB, ni mogoče prenesti"</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index f3dc95a..efb4f0f 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Audioja e \"bluetooth-it\" e lidhur"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Audioja e \"bluetooth-it\" e shkëputur"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audioja e \"bluetooth-it\""</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Skedarët më të mëdhenj se 4 GB nuk mund të transferohen"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index c1e9930..789c242 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Не могу да се преносе датотеке веће од 4 GB"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 5fee78e..de55790 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth-ljud är anslutet"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-ljud är frånkopplat"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-ljud"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Det går inte att överföra filer som är större än 4 GB"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 0ed66af..705984a 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 ni tupu."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"Historia ya uhamishaji haina chochote."</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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Imeunganisha sauti ya Bluetooth"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Imeondoa sauti ya Bluetooth"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Sauti ya Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Haiwezi kutuma faili zinazozidi GB 4"</string>
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 1036300..8e9b716 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -17,9 +17,9 @@
 <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="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare நிர்வாகியை அணுகுவதற்குப் ஆப்ஸை அனுமதித்து,  கோப்புகளைப் பரிமாற்ற அதைப் பயன்படுத்துகிறது."</string>
     <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ஏற்புபட்டியல் புளூடூத் சாதன அணுகல்."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"புளூடூத் சாதனத்தைத் தற்காலிகமாக ஏற்புபட்டியலில் சேர்க்க பயன்பாட்டை அனுமதிக்கிறது, பயனரின் உறுதிபடுத்தலின்றி கோப்புகளை இந்தச் சாதனத்திற்கு அனுப்ப அதை அனுமதிக்கிறது."</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>
@@ -132,4 +132,5 @@
     <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">"4ஜி.பை.க்கு மேலிருக்கும் ஃபைல்களை இடமாற்ற முடியாது"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 8163e92..79ea712 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -132,4 +132,5 @@
     <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-th/strings.xml b/res/values-th/strings.xml
index 59abff1..0fdd7db 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -23,7 +23,7 @@
     <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_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>
@@ -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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"เชื่อมต่อ Bluetooth Audio แล้ว"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ยกเลิกการเชื่อมต่อ Bluetooth Audio แล้ว"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"โอนไฟล์ที่มีขนาดใหญ่กว่า 4 GB ไม่ได้"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index acc5752..4c7059e 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Nakakonekta ang Bluetooth audio"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Nadiskonekta ang Bluetooth audio"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Hindi maililipat ang mga file na mas malaki sa 4GB"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 40441fa..2d2fd00 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth ses bağlandı"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth ses bağlantısı kesildi"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Ses"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB\'tan büyük dosyalar aktarılamaz"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index e183246..cbb9f66 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -136,4 +136,5 @@
     <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 Audio"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Не можна перенести файли, більші за 4 ГБ"</string>
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 14108f4..3e46987 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -132,4 +132,5 @@
     <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-uz/strings.xml b/res/values-uz/strings.xml
index 402cb73..88535c5 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">"Yashirish"</string>
+    <string name="download_ok" msgid="5000360731674466039">"Berkitish"</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">"O‘tkazmalar tarixi bo‘m-bo‘sh."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"Hech narsa topilmadi."</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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Bluetooth orqali ovoz yoqildi"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth orqali ovoz o‘chirildi"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth orqali ovoz"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GBdan katta hajmli videolar o‘tkazilmaydi"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 62edba8..0be8dea 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">"Lịch sử chuyển trống."</string>
+    <string name="no_transfers" msgid="3482965619151865672">"Không có nội dung trong lịch sử truyền."</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>
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Đã kết nối âm thanh Bluetooth"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Đã ngắt kết nối âm thanh Bluetooth"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Âm thanh Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Không thể chuyển những tệp lớn hơn 4 GB"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index c44def0..8440c77 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -132,4 +132,5 @@
     <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-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index a896651..9abb914 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/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>
@@ -132,4 +132,5 @@
     <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">"無法轉移 4 GB 以上的檔案"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index a010f16..4220c84 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>
@@ -132,4 +132,5 @@
     <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-zh-rTW/test_strings.xml b/res/values-zh-rTW/test_strings.xml
index 2d2d57b..dd2369a 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-zu/strings.xml b/res/values-zu/strings.xml
index e5bedb1..79c3ca2 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -132,4 +132,5 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"Umsindo we-Bluetooth uxhunyiwe"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"Umsindo we-Bluetooth unqanyuliwe"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Umsindo we-Bluetooth"</string>
+    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Amafayela amakhulu kuno-4GB awakwazi ukudluliselwa"</string>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 05448c5..9301e9d 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -18,7 +18,7 @@
     <bool name="profile_supported_hdp">true</bool>
     <bool name="profile_supported_hs_hfp">true</bool>
     <bool name="profile_supported_hfpclient">false</bool>
-    <bool name="profile_supported_hid">true</bool>
+    <bool name="profile_supported_hid_host">true</bool>
     <bool name="profile_supported_opp">true</bool>
     <bool name="profile_supported_pan">true</bool>
     <bool name="profile_supported_pbap">true</bool>
@@ -26,11 +26,13 @@
     <bool name="pbap_include_photos_in_vcard">true</bool>
     <bool name="pbap_use_profile_for_owner_vcard">true</bool>
     <bool name="profile_supported_map">true</bool>
+    <bool name="profile_supported_avrcp_target">true</bool>
     <bool name="profile_supported_avrcp_controller">false</bool>
     <bool name="profile_supported_sap">false</bool>
     <bool name="profile_supported_pbapclient">false</bool>
     <bool name="profile_supported_mapmce">false</bool>
-    <bool name="profile_supported_hidd">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
@@ -100,4 +102,7 @@
           - BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL to cancel access requests -->
     <string name="pairing_ui_package">com.android.settings</string>
 
+    <!-- Flag whether or not to keep polling AG with CLCC for call information every 2 seconds -->
+    <bool name="hfp_clcc_poll_during_call">true</bool>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8ba7fd0..17023e4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -247,4 +247,5 @@
     <string name="bluetooth_connected">Bluetooth audio connected</string>
     <string name="bluetooth_disconnected">Bluetooth audio disconnected"</string>
     <string name="a2dp_sink_mbs_label">Bluetooth Audio</string>
+    <string name="bluetooth_opp_file_limit_exceeded">Files bigger than 4GB cannot be transferred</string>
 </resources>
diff --git a/src/com/android/bluetooth/BluetoothObexTransport.java b/src/com/android/bluetooth/BluetoothObexTransport.java
index 216ced2..e5683e9 100644
--- a/src/com/android/bluetooth/BluetoothObexTransport.java
+++ b/src/com/android/bluetooth/BluetoothObexTransport.java
@@ -36,35 +36,44 @@
         this.mSocket = socket;
     }
 
+    @Override
     public void close() throws IOException {
         mSocket.close();
     }
 
+    @Override
     public DataInputStream openDataInputStream() throws IOException {
         return new DataInputStream(openInputStream());
     }
 
+    @Override
     public DataOutputStream openDataOutputStream() throws IOException {
         return new DataOutputStream(openOutputStream());
     }
 
+    @Override
     public InputStream openInputStream() throws IOException {
         return mSocket.getInputStream();
     }
 
+    @Override
     public OutputStream openOutputStream() throws IOException {
         return mSocket.getOutputStream();
     }
 
+    @Override
     public void connect() throws IOException {
     }
 
+    @Override
     public void create() throws IOException {
     }
 
+    @Override
     public void disconnect() throws IOException {
     }
 
+    @Override
     public void listen() throws IOException {
     }
 
@@ -72,13 +81,15 @@
         return true;
     }
 
+    @Override
     public int getMaxTransmitPacketSize() {
         if (mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
-           return -1;
+            return -1;
         }
         return mSocket.getMaxTransmitPacketSize();
     }
 
+    @Override
     public int getMaxReceivePacketSize() {
         if (mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
             return -1;
@@ -87,14 +98,15 @@
     }
 
     public String getRemoteAddress() {
-        if (mSocket == null)
+        if (mSocket == null) {
             return null;
+        }
         return mSocket.getRemoteDevice().getAddress();
     }
 
     @Override
     public boolean isSrmSupported() {
-        if(mSocket.getConnectionType() == BluetoothSocket.TYPE_L2CAP) {
+        if (mSocket.getConnectionType() == BluetoothSocket.TYPE_L2CAP) {
             return true;
         }
         return false;
diff --git a/src/com/android/bluetooth/DeviceWorkArounds.java b/src/com/android/bluetooth/DeviceWorkArounds.java
new file mode 100644
index 0000000..07f86e5
--- /dev/null
+++ b/src/com/android/bluetooth/DeviceWorkArounds.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * @hide
+ */
+public final class DeviceWorkArounds {
+    public static final String PCM_CARKIT = "9C:DF:03";
+    public static final String FORD_SYNC_CARKIT = "00:1E:AE";
+    public static final String HONDA_CARKIT = "64:D4:BD";
+    public static final String SYNC_CARKIT = "D0:39:72";
+    public static final String BREZZA_ZDI_CARKIT = "28:a1:83";
+    public static final String MERCEDES_BENZ_CARKIT = "00:26:e8";
+
+    /**
+     * @hide
+     */
+    public static boolean addressStartsWith(String bdAddr, String carkitAddr) {
+        return bdAddr.toLowerCase().startsWith(carkitAddr.toLowerCase());
+    }
+}
diff --git a/src/com/android/bluetooth/IObexConnectionHandler.java b/src/com/android/bluetooth/IObexConnectionHandler.java
index 87c60b5..66f078d 100644
--- a/src/com/android/bluetooth/IObexConnectionHandler.java
+++ b/src/com/android/bluetooth/IObexConnectionHandler.java
@@ -27,7 +27,7 @@
      * @return Shall return TRUE if the connection should be accepted.
      * FALSE otherwise
      */
-    public boolean onConnect(BluetoothDevice device, BluetoothSocket socket);
+    boolean onConnect(BluetoothDevice device, BluetoothSocket socket);
 
     /**
      * Will be called in case the accept call fails.
@@ -35,5 +35,5 @@
      * The behavior needed is to shutdown the ObexServerSockets object, and create a
      * new one.
      */
-    public void onAcceptFailed();
+    void onAcceptFailed();
 }
diff --git a/src/com/android/bluetooth/ObexRejectServer.java b/src/com/android/bluetooth/ObexRejectServer.java
index 7cfa0a3..98334f7 100644
--- a/src/com/android/bluetooth/ObexRejectServer.java
+++ b/src/com/android/bluetooth/ObexRejectServer.java
@@ -15,11 +15,6 @@
 
 package com.android.bluetooth;
 
-import java.io.IOException;
-
-import javax.obex.HeaderSet;
-import javax.obex.ServerRequestHandler;
-
 import android.bluetooth.BluetoothSocket;
 import android.os.Handler;
 import android.os.Handler.Callback;
@@ -28,6 +23,11 @@
 import android.os.Message;
 import android.util.Log;
 
+import java.io.IOException;
+
+import javax.obex.HeaderSet;
+import javax.obex.ServerRequestHandler;
+
 /**
  * A simple ObexServer used to handle connection rejection in two cases:
  *  - A profile cannot handle a new connection, as it is already connected to another device.
@@ -42,8 +42,8 @@
     private final int mResult;
     private final HandlerThread mHandlerThread;
     private final Handler mMessageHandler;
-    private final static int MSG_ID_TIMEOUT = 0x01;
-    private final static int TIMEOUT_VALUE = 5*1000; // ms
+    private static final int MSG_ID_TIMEOUT = 0x01;
+    private static final int TIMEOUT_VALUE = 5 * 1000; // ms
     private final BluetoothSocket mSocket;
 
     /**
@@ -65,31 +65,35 @@
     // OBEX operation handlers
     @Override
     public int onConnect(HeaderSet request, HeaderSet reply) {
-        if(V) Log.i(TAG,"onConnect() returning error");
+        if (V) {
+            Log.i(TAG, "onConnect() returning error");
+        }
         return mResult;
     }
 
     public void shutdown() {
-      mMessageHandler.removeCallbacksAndMessages(null);
-      mHandlerThread.quit();
-      try {
-          // This will cause an exception in the ServerSession, causing it to shut down
-          mSocket.close();
-      } catch (IOException e) {
-          Log.w(TAG, "Unable to close socket - ignoring", e);
-      }
+        mMessageHandler.removeCallbacksAndMessages(null);
+        mHandlerThread.quit();
+        try {
+            // This will cause an exception in the ServerSession, causing it to shut down
+            mSocket.close();
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to close socket - ignoring", e);
+        }
     }
 
     @Override
     public boolean handleMessage(Message msg) {
-        if(V) Log.i(TAG,"Handling message ID: " + msg.what);
-        switch(msg.what) {
-        case MSG_ID_TIMEOUT:
-            shutdown();
-            break;
-        default:
-            // Message not handled
-            return false;
+        if (V) {
+            Log.i(TAG, "Handling message ID: " + msg.what);
+        }
+        switch (msg.what) {
+            case MSG_ID_TIMEOUT:
+                shutdown();
+                break;
+            default:
+                // Message not handled
+                return false;
         }
         return true; // Message handled
     }
diff --git a/src/com/android/bluetooth/ObexServerSockets.java b/src/com/android/bluetooth/ObexServerSockets.java
index 93766b2..789f12f 100644
--- a/src/com/android/bluetooth/ObexServerSockets.java
+++ b/src/com/android/bluetooth/ObexServerSockets.java
@@ -14,17 +14,17 @@
 */
 package com.android.bluetooth;
 
-import java.io.IOException;
-
-import javax.obex.ResponseCodes;
-import javax.obex.ServerSession;
-
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothServerSocket;
 import android.bluetooth.BluetoothSocket;
 import android.util.Log;
 
+import java.io.IOException;
+
+import javax.obex.ResponseCodes;
+import javax.obex.ServerSession;
+
 /**
  * Wraps multiple BluetoothServerSocket objects to make it possible to accept connections on
  * both a RFCOMM and L2CAP channel in parallel.<br>
@@ -45,7 +45,7 @@
  * In both cases the {@link ObexServerSockets} object have terminated, and a new must be created.
  */
 public class ObexServerSockets {
-    private final String TAG;
+    private final String mTag;
     private static final String STAG = "ObexServerSockets";
     private static final boolean D = true; // TODO: set to false!
     private static final int NUMBER_OF_SOCKET_TYPES = 2; // increment if LE will be supported
@@ -55,20 +55,17 @@
     private final BluetoothServerSocket mRfcommSocket;
     private final BluetoothServerSocket mL2capSocket;
     /* Handles to the accept threads. Needed for shutdown. */
-    private SocketAcceptThread mRfcommThread = null;
-    private SocketAcceptThread mL2capThread = null;
+    private SocketAcceptThread mRfcommThread;
+    private SocketAcceptThread mL2capThread;
 
-    private volatile boolean mConAccepted = false;
+    private static volatile int sInstanceCounter;
 
-    private static volatile int sInstanceCounter = 0;
-
-    private ObexServerSockets(IObexConnectionHandler conHandler,
-            BluetoothServerSocket rfcommSocket,
+    private ObexServerSockets(IObexConnectionHandler conHandler, BluetoothServerSocket rfcommSocket,
             BluetoothServerSocket l2capSocket) {
         mConHandler = conHandler;
         mRfcommSocket = rfcommSocket;
         mL2capSocket = l2capSocket;
-        TAG = "ObexServerSockets" + sInstanceCounter++;
+        mTag = "ObexServerSockets" + sInstanceCounter++;
     }
 
     /**
@@ -85,7 +82,7 @@
 
     /**
      * Creates an Insecure RFCOMM {@link BluetoothServerSocket} and a L2CAP
-         *                  {@link BluetoothServerSocket}
+     *                  {@link BluetoothServerSocket}
      * @param validator a reference to the {@link IObexConnectionHandler} object to call
      *                  to validate an incoming connection.
      * @return a reference to a {@link ObexServerSockets} object instance.
@@ -96,6 +93,8 @@
                 BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false);
     }
 
+    private static final int CREATE_RETRY_TIME = 10;
+
     /**
      * Creates an RFCOMM {@link BluetoothServerSocket} and a L2CAP {@link BluetoothServerSocket}
      * with specific l2cap and RFCOMM channel numbers. It is the responsibility of the caller to
@@ -110,30 +109,31 @@
      * TODO: Make public when it becomes possible to determine that the listen-call
      *       failed due to channel-in-use.
      */
-    private static ObexServerSockets create(
-            IObexConnectionHandler validator, int rfcommChannel, int l2capPsm, boolean isSecure) {
-        if(D) Log.d(STAG,"create(rfcomm = " +rfcommChannel + ", l2capPsm = " + l2capPsm +")");
+    private static ObexServerSockets create(IObexConnectionHandler validator, int rfcommChannel,
+            int l2capPsm, boolean isSecure) {
+        if (D) {
+            Log.d(STAG, "create(rfcomm = " + rfcommChannel + ", l2capPsm = " + l2capPsm + ")");
+        }
         BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
-        if(bt == null) {
+        if (bt == null) {
             throw new RuntimeException("No bluetooth adapter...");
         }
         BluetoothServerSocket rfcommSocket = null;
         BluetoothServerSocket l2capSocket = null;
         boolean initSocketOK = false;
-        final int CREATE_RETRY_TIME = 10;
 
         // It's possible that create will fail in some cases. retry for 10 times
         for (int i = 0; i < CREATE_RETRY_TIME; i++) {
             initSocketOK = true;
             try {
-                if(rfcommSocket == null) {
+                if (rfcommSocket == null) {
                     if (isSecure) {
                         rfcommSocket = bt.listenUsingRfcommOn(rfcommChannel);
                     } else {
                         rfcommSocket = bt.listenUsingInsecureRfcommOn(rfcommChannel);
                     }
                 }
-                if(l2capSocket == null) {
+                if (l2capSocket == null) {
                     if (isSecure) {
                         l2capSocket = bt.listenUsingL2capOn(l2capPsm);
                     } else {
@@ -141,19 +141,21 @@
                     }
                 }
             } catch (IOException e) {
-                Log.e(STAG, "Error create ServerSockets ",e);
+                Log.e(STAG, "Error create ServerSockets ", e);
                 initSocketOK = false;
             }
             if (!initSocketOK) {
                 // Need to break out of this loop if BT is being turned off.
                 int state = bt.getState();
-                if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
-                    (state != BluetoothAdapter.STATE_ON)) {
+                if ((state != BluetoothAdapter.STATE_TURNING_ON) && (state
+                        != BluetoothAdapter.STATE_ON)) {
                     Log.w(STAG, "initServerSockets failed as BT is (being) turned off");
                     break;
                 }
                 try {
-                    if (D) Log.v(STAG, "waiting 300 ms...");
+                    if (D) {
+                        Log.v(STAG, "waiting 300 ms...");
+                    }
                     Thread.sleep(300);
                 } catch (InterruptedException e) {
                     Log.e(STAG, "create() was interrupted");
@@ -164,7 +166,9 @@
         }
 
         if (initSocketOK) {
-            if (D) Log.d(STAG, "Succeed to create listening sockets ");
+            if (D) {
+                Log.d(STAG, "Succeed to create listening sockets ");
+            }
             ObexServerSockets sockets = new ObexServerSockets(validator, rfcommSocket, l2capSocket);
             sockets.startAccept();
             return sockets;
@@ -198,8 +202,9 @@
      * the {@link IObexConnectionValidator#onConnect()}, at which point both threads will exit.
      */
     private void startAccept() {
-        if(D) Log.d(TAG,"startAccept()");
-        prepareForNewConnect();
+        if (D) {
+            Log.d(mTag, "startAccept()");
+        }
 
         mRfcommThread = new SocketAcceptThread(mRfcommSocket);
         mRfcommThread.start();
@@ -209,42 +214,26 @@
     }
 
     /**
-     * Set state to accept new incoming connection. Will cause the next incoming connection to be
-     * Signaled through {@link IObexConnectionValidator#onConnect()};
-     */
-    synchronized public void prepareForNewConnect() {
-        if(D) Log.d(TAG, "prepareForNewConnect()");
-        mConAccepted = false;
-    }
-
-    /**
      * Called from the AcceptThreads to signal an incoming connection.
-     * This is the entry point that needs to synchronize between the accept
-     * threads, and ensure only a single connection is accepted.
-     * {@link mAcceptedSocket} is used a state variable.
      * @param device the connecting device.
      * @param conSocket the socket associated with the connection.
      * @return true if the connection is accepted, false otherwise.
      */
-    synchronized private boolean onConnect(BluetoothDevice device, BluetoothSocket conSocket) {
-        if(D) Log.d(TAG, "onConnect() socket: " + conSocket + " mConAccepted = " + mConAccepted);
-        if(mConAccepted  == false && mConHandler.onConnect(device, conSocket) == true) {
-            mConAccepted = true; // TODO: Reset this when ready to accept new connection
-            /* Signal the remaining threads to stop.
-            shutdown(false); */ // UPDATE: TODO: remove - redesigned to keep running...
-            return true;
+    private synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket conSocket) {
+        if (D) {
+            Log.d(mTag, "onConnect() socket: " + conSocket);
         }
-        return false;
+        return mConHandler.onConnect(device, conSocket);
     }
 
     /**
      * Signal to the {@link IObexConnectionHandler} that an error have occurred.
      */
-    synchronized private void onAcceptFailed() {
+    private synchronized void onAcceptFailed() {
         shutdown(false);
         BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
         if ((mAdapter != null) && (mAdapter.getState() == BluetoothAdapter.STATE_ON)) {
-            Log.d(TAG, "onAcceptFailed() calling shutdown...");
+            Log.d(mTag, "onAcceptFailed() calling shutdown...");
             mConHandler.onAcceptFailed();
         }
     }
@@ -254,27 +243,29 @@
      * @param block Set true to block the calling thread until the AcceptThreads
      * has ended execution
      */
-    synchronized public void shutdown(boolean block) {
-        if(D) Log.d(TAG, "shutdown(block = " + block + ")");
-        if(mRfcommThread != null) {
+    public synchronized void shutdown(boolean block) {
+        if (D) {
+            Log.d(mTag, "shutdown(block = " + block + ")");
+        }
+        if (mRfcommThread != null) {
             mRfcommThread.shutdown();
         }
-        if(mL2capThread != null){
+        if (mL2capThread != null) {
             mL2capThread.shutdown();
         }
-        if(block == true) {
-            while(mRfcommThread != null || mL2capThread != null) {
+        if (block) {
+            while (mRfcommThread != null || mL2capThread != null) {
                 try {
-                    if(mRfcommThread != null) {
+                    if (mRfcommThread != null) {
                         mRfcommThread.join();
                         mRfcommThread = null;
                     }
-                    if(mL2capThread != null) {
+                    if (mL2capThread != null) {
                         mL2capThread.join();
                         mL2capThread = null;
                     }
                 } catch (InterruptedException e) {
-                    Log.i(TAG, "shutdown() interrupted, continue waiting...", e);
+                    Log.i(mTag, "shutdown() interrupted, continue waiting...", e);
                 }
             }
         } else {
@@ -300,8 +291,8 @@
          * @param latch shall never be null.
          * @throws IllegalArgumentException
          */
-        public SocketAcceptThread(BluetoothServerSocket serverSocket) {
-            if(serverSocket == null) {
+        SocketAcceptThread(BluetoothServerSocket serverSocket) {
+            if (serverSocket == null) {
                 throw new IllegalArgumentException("serverSocket cannot be null");
             }
             mServerSocket = serverSocket;
@@ -319,24 +310,28 @@
                     BluetoothDevice device;
 
                     try {
-                        if (D) Log.d(TAG, "Accepting socket connection...");
+                        if (D) {
+                            Log.d(mTag, "Accepting socket connection...");
+                        }
 
                         connSocket = mServerSocket.accept();
-                        if (D) Log.d(TAG, "Accepted socket connection from: " + mServerSocket);
+                        if (D) {
+                            Log.d(mTag, "Accepted socket connection from: " + mServerSocket);
+                        }
 
-                       if (connSocket == null) {
-                           // TODO: Do we need a max error count, to avoid spinning?
-                            Log.w(TAG, "connSocket is null - reattempt accept");
+                        if (connSocket == null) {
+                            // TODO: Do we need a max error count, to avoid spinning?
+                            Log.w(mTag, "connSocket is null - reattempt accept");
                             continue;
                         }
                         device = connSocket.getRemoteDevice();
 
                         if (device == null) {
-                            Log.i(TAG, "getRemoteDevice() = null - reattempt accept");
-                            try{
+                            Log.i(mTag, "getRemoteDevice() = null - reattempt accept");
+                            try {
                                 connSocket.close();
                             } catch (IOException e) {
-                                Log.w(TAG, "Error closing the socket. ignoring...",e );
+                                Log.w(mTag, "Error closing the socket. ignoring...", e);
                             }
                             continue;
                         }
@@ -345,37 +340,36 @@
                          */
                         boolean isValid = ObexServerSockets.this.onConnect(device, connSocket);
 
-                        if(isValid == false) {
+                        if (!isValid) {
                             /* Close connection if we already have a connection with another device
                              * by responding to the OBEX connect request.
                              */
-                            Log.i(TAG, "RemoteDevice is invalid - creating ObexRejectServer.");
+                            Log.i(mTag, "RemoteDevice is invalid - creating ObexRejectServer.");
                             BluetoothObexTransport obexTrans =
                                     new BluetoothObexTransport(connSocket);
                             // Create and detach a selfdestructing ServerSession to respond to any
                             // incoming OBEX signals.
                             new ServerSession(obexTrans,
-                                    new ObexRejectServer(
-                                            ResponseCodes.OBEX_HTTP_UNAVAILABLE,
-                                            connSocket),
-                                    null);
+                                    new ObexRejectServer(ResponseCodes.OBEX_HTTP_UNAVAILABLE,
+                                            connSocket), null);
                             // now wait for a new connect
                         } else {
                             // now wait for a new connect
                         }
                     } catch (IOException ex) {
-                        if(mStopped == true) {
+                        if (mStopped) {
                             // Expected exception because of shutdown.
                         } else {
-                            Log.w(TAG, "Accept exception for " +
-                                    mServerSocket, ex);
+                            Log.w(mTag, "Accept exception for " + mServerSocket, ex);
                             ObexServerSockets.this.onAcceptFailed();
                         }
-                        mStopped=true;
+                        mStopped = true;
                     }
                 } // End while()
             } finally {
-                if (D) Log.d(TAG, "AcceptThread ended for: " + mServerSocket);
+                if (D) {
+                    Log.d(mTag, "AcceptThread ended for: " + mServerSocket);
+                }
             }
         }
 
@@ -385,22 +379,26 @@
          * are ready to be disconnected.
          */
         public void shutdown() {
-            if(mStopped == false) {
+            if (!mStopped) {
                 mStopped = true;
                 // TODO: According to the documentation, this should not close the accepted
                 //       sockets - and that is true, but it closes the l2cap connections, and
                 //       therefore it implicitly also closes the accepted sockets...
                 try {
-                     mServerSocket.close();
+                    mServerSocket.close();
                 } catch (IOException e) {
-                    if(D) Log.d(TAG, "Exception while thread shutdown:", e);
+                    if (D) {
+                        Log.d(mTag, "Exception while thread shutdown:", e);
+                    }
                 }
             }
             // If called from another thread, interrupt the thread
-            if(!Thread.currentThread().equals(this)){
+            if (!Thread.currentThread().equals(this)) {
                 // TODO: Will this interrupt the thread if it is blocked in synchronized?
                 // Else: change to use InterruptableLock
-                if(D) Log.d(TAG, "shutdown called from another thread - interrupt().");
+                if (D) {
+                    Log.d(mTag, "shutdown called from another thread - interrupt().");
+                }
                 interrupt();
             }
         }
diff --git a/src/com/android/bluetooth/SignedLongLong.java b/src/com/android/bluetooth/SignedLongLong.java
index de18fbb..ec8f293 100644
--- a/src/com/android/bluetooth/SignedLongLong.java
+++ b/src/com/android/bluetooth/SignedLongLong.java
@@ -47,17 +47,19 @@
         long lsb = 0, msb = 0;
 
         lsbStr = msbStr = null;
-        if(value == null) throw new NullPointerException();
-        value=value.trim();
+        if (value == null) {
+            throw new NullPointerException();
+        }
+        value = value.trim();
         int valueLength = value.length();
-        if(valueLength == 0 || valueLength > 32) {
+        if (valueLength == 0 || valueLength > 32) {
             throw new NumberFormatException("invalid string length: " + valueLength);
         }
-        if(valueLength <= 16){
+        if (valueLength <= 16) {
             lsbStr = value;
         } else {
-            lsbStr = value.substring(valueLength-16, valueLength);
-            msbStr = value.substring(0, valueLength-16);
+            lsbStr = value.substring(valueLength - 16, valueLength);
+            msbStr = value.substring(0, valueLength - 16);
             msb = BluetoothMapUtils.getLongFromString(msbStr);
         }
         lsb = BluetoothMapUtils.getLongFromString(lsbStr);
@@ -66,16 +68,16 @@
 
     @Override
     public int compareTo(SignedLongLong another) {
-        if(mMostSigBits == another.mMostSigBits) {
-            if(mLeastSigBits == another.mLeastSigBits) {
+        if (mMostSigBits == another.mMostSigBits) {
+            if (mLeastSigBits == another.mLeastSigBits) {
                 return 0;
             }
-            if(mLeastSigBits < another.mLeastSigBits) {
+            if (mLeastSigBits < another.mLeastSigBits) {
                 return -1;
             }
             return 1;
         }
-        if(mMostSigBits < another.mMostSigBits) {
+        if (mMostSigBits < another.mMostSigBits) {
             return -1;
         }
         return 1;
@@ -90,7 +92,7 @@
      *
      * @return a hex-string representation of the object values
      */
-    public String toHexString(){
+    public String toHexString() {
         return BluetoothMapUtils.getLongLongAsString(mLeastSigBits, mMostSigBits);
     }
 
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index 920b025..457b053 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -17,7 +17,6 @@
 package com.android.bluetooth;
 
 import android.app.ActivityManager;
-import android.app.ActivityThread;
 import android.app.AppOpsManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -29,6 +28,7 @@
 import android.os.Build;
 import android.os.ParcelUuid;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -46,9 +46,10 @@
  * @hide
  */
 
-final public class Utils {
+public final class Utils {
     private static final String TAG = "BluetoothUtils";
     private static final int MICROS_PER_UNIT = 625;
+    private static final String PTS_TEST_MODE_PROPERTY = "persist.bluetooth.pts";
 
     static final int BD_ADDR_LEN = 6; // bytes
     static final int BD_UUID_LEN = 16; // bytes
@@ -58,9 +59,8 @@
             return null;
         }
 
-        return String.format("%02X:%02X:%02X:%02X:%02X:%02X",
-                address[0], address[1], address[2], address[3], address[4],
-                address[5]);
+        return String.format("%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1], address[2],
+                address[3], address[4], address[5]);
     }
 
     public static byte[] getByteAddress(BluetoothDevice device) {
@@ -101,7 +101,9 @@
     public static String byteArrayToString(byte[] valueBuf) {
         StringBuilder sb = new StringBuilder();
         for (int idx = 0; idx < valueBuf.length; idx++) {
-            if (idx != 0) sb.append(" ");
+            if (idx != 0) {
+                sb.append(" ");
+            }
             sb.append(String.format("%02x", valueBuf[idx]));
         }
         return sb.toString();
@@ -153,8 +155,8 @@
         converter.order(ByteOrder.BIG_ENDIAN);
 
         for (int i = 0; i < numUuids; i++) {
-            puuids[i] = new ParcelUuid(new UUID(converter.getLong(offset),
-                    converter.getLong(offset + 8)));
+            puuids[i] = new ParcelUuid(
+                    new UUID(converter.getLong(offset), converter.getLong(offset + 8)));
             offset += BD_UUID_LEN;
         }
         return puuids;
@@ -177,9 +179,15 @@
 
     public static String ellipsize(String s) {
         // Only ellipsize release builds
-        if (!Build.TYPE.equals("user")) return s;
-        if (s == null) return null;
-        if (s.length() < 3) return s;
+        if (!Build.TYPE.equals("user")) {
+            return s;
+        }
+        if (s == null) {
+            return null;
+        }
+        if (s.length() < 3) {
+            return s;
+        }
         return s.charAt(0) + "⋯" + s.charAt(s.length() - 1);
     }
 
@@ -214,67 +222,46 @@
         }
     }
 
+    static int sSystemUiUid = UserHandle.USER_NULL;
+    public static void setSystemUiUid(int uid) {
+        Utils.sSystemUiUid = uid;
+    }
+
+    static int sForegroundUserId = UserHandle.USER_NULL;
+    public static void setForegroundUserId(int uid) {
+        Utils.sForegroundUserId = uid;
+    }
+
     public static boolean checkCaller() {
-        boolean ok;
-        // Get the caller's user id then clear the calling identity
-        // which will be restored in the finally clause.
         int callingUser = UserHandle.getCallingUserId();
         int callingUid = Binder.getCallingUid();
-        long ident = Binder.clearCallingIdentity();
-
-        try {
-            // With calling identity cleared the current user is the foreground user.
-            int foregroundUser = ActivityManager.getCurrentUser();
-            ok = (foregroundUser == callingUser);
-            if (!ok) {
-                // Always allow SystemUI/System access.
-                final int systemUiUid = ActivityThread.getPackageManager().getPackageUid(
-                        "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
-                        UserHandle.USER_SYSTEM);
-                ok = (systemUiUid == callingUid) || (Process.SYSTEM_UID == callingUid);
-            }
-        } catch (Exception ex) {
-            Log.e(TAG, "checkIfCallerIsSelfOrForegroundUser: Exception ex=" + ex);
-            ok = false;
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-        return ok;
+        return (sForegroundUserId == callingUser) || (sSystemUiUid == callingUid)
+                || (Process.SYSTEM_UID == callingUid);
     }
 
     public static boolean checkCallerAllowManagedProfiles(Context mContext) {
         if (mContext == null) {
             return checkCaller();
         }
-        boolean ok;
-        // Get the caller's user id and if it's a managed profile, get it's parents
-        // id, then clear the calling identity
-        // which will be restored in the finally clause.
         int callingUser = UserHandle.getCallingUserId();
         int callingUid = Binder.getCallingUid();
+
+        // Use the Bluetooth process identity when making call to get parent user
         long ident = Binder.clearCallingIdentity();
         try {
             UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
             UserInfo ui = um.getProfileParent(callingUser);
             int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;
-            // With calling identity cleared the current user is the foreground user.
-            int foregroundUser = ActivityManager.getCurrentUser();
-            ok = (foregroundUser == callingUser) ||
-                    (foregroundUser == parentUser);
-            if (!ok) {
-                // Always allow SystemUI/System access.
-                final int systemUiUid = ActivityThread.getPackageManager().getPackageUid(
-                        "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
-                        UserHandle.USER_SYSTEM);
-                ok = (systemUiUid == callingUid) || (Process.SYSTEM_UID == callingUid);
-            }
+
+            // Always allow SystemUI/System access.
+            return (sForegroundUserId == callingUser) || (sForegroundUserId == parentUser)
+                    || (sSystemUiUid == callingUid) || (Process.SYSTEM_UID == callingUid);
         } catch (Exception ex) {
             Log.e(TAG, "checkCallerAllowManagedProfiles: Exception ex=" + ex);
-            ok = false;
+            return false;
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
-        return ok;
     }
 
     /**
@@ -295,15 +282,15 @@
      */
     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)) {
+        if (context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_FINE_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)) {
+        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
@@ -330,8 +317,8 @@
      * Returns true if the caller holds PEERS_MAC_ADDRESS.
      */
     public static boolean checkCallerHasPeersMacAddressPermission(Context context) {
-        return context.checkCallingOrSelfPermission(
-                android.Manifest.permission.PEERS_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED;
+        return context.checkCallingOrSelfPermission(android.Manifest.permission.PEERS_MAC_ADDRESS)
+                == PackageManager.PERMISSION_GRANTED;
     }
 
     public static boolean isLegacyForegroundApp(Context context, String pkgName) {
@@ -340,8 +327,8 @@
 
     private static boolean isMApp(Context context, String pkgName) {
         try {
-            return context.getPackageManager().getApplicationInfo(pkgName, 0)
-                    .targetSdkVersion >= Build.VERSION_CODES.M;
+            return context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
+                    >= Build.VERSION_CODES.M;
         } catch (PackageManager.NameNotFoundException e) {
             // In case of exception, assume M app
         }
@@ -354,7 +341,7 @@
      * @param pkgName application package name.
      */
     private static boolean isForegroundApp(Context context, String pkgName) {
-        ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
+        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());
     }
@@ -370,4 +357,50 @@
     public static int millsToUnit(int milliseconds) {
         return (int) (TimeUnit.MILLISECONDS.toMicros(milliseconds) / MICROS_PER_UNIT);
     }
+
+    /**
+     * Check if we are running in BluetoothInstrumentationTest context by trying to load
+     * com.android.bluetooth.FileSystemWriteTest. If we are not in Instrumentation test mode, this
+     * class should not be found. Thus, the assumption is that FileSystemWriteTest must exist.
+     * If FileSystemWriteTest is removed in the future, another test class in
+     * BluetoothInstrumentationTest should be used instead
+     *
+     * @return true if in BluetoothInstrumentationTest, false otherwise
+     */
+    public static boolean isInstrumentationTestMode() {
+        try {
+            return Class.forName("com.android.bluetooth.FileSystemWriteTest") != null;
+        } catch (ClassNotFoundException exception) {
+            return false;
+        }
+    }
+
+    /**
+     * Throws {@link IllegalStateException} if we are not in BluetoothInstrumentationTest. Useful
+     * for ensuring certain methods only get called in BluetoothInstrumentationTest
+     */
+    public static void enforceInstrumentationTestMode() {
+        if (!isInstrumentationTestMode()) {
+            throw new IllegalStateException("Not in BluetoothInstrumentationTest");
+        }
+    }
+
+    /**
+     * Check if we are running in PTS test mode. To enable/disable PTS test mode, invoke
+     * {@code adb shell setprop persist.bluetooth.pts true/false}
+     *
+     * @return true if in PTS Test mode, false otherwise
+     */
+    public static boolean isPtsTestMode() {
+        return SystemProperties.getBoolean(PTS_TEST_MODE_PROPERTY, false);
+    }
+
+    /**
+     * Get uid/pid string in a binder call
+     *
+     * @return "uid/pid=xxxx/yyyy"
+     */
+    public static String getUidPidString() {
+        return "uid/pid=" + Binder.getCallingUid() + "/" + Binder.getCallingPid();
+    }
 }
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
new file mode 100644
index 0000000..4de89df
--- /dev/null
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -0,0 +1,190 @@
+/*
+ * 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.a2dp;
+
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+
+import com.android.bluetooth.R;
+
+/*
+ * A2DP Codec Configuration setup.
+ */
+class A2dpCodecConfig {
+    private static final boolean DBG = true;
+    private static final String TAG = "A2dpCodecConfig";
+
+    private Context mContext;
+    private A2dpNativeInterface mA2dpNativeInterface;
+
+    private BluetoothCodecConfig[] mCodecConfigPriorities;
+    private int mA2dpSourceCodecPrioritySbc = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    private int mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    private int mA2dpSourceCodecPriorityAptx = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    private int mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    private int mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+
+    A2dpCodecConfig(Context context, A2dpNativeInterface a2dpNativeInterface) {
+        mContext = context;
+        mA2dpNativeInterface = a2dpNativeInterface;
+        mCodecConfigPriorities = assignCodecConfigPriorities();
+    }
+
+    BluetoothCodecConfig[] codecConfigPriorities() {
+        return mCodecConfigPriorities;
+    }
+
+    void setCodecConfigPreference(BluetoothDevice device,
+                                  BluetoothCodecConfig codecConfig) {
+        BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
+        codecConfigArray[0] = codecConfig;
+        mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
+    }
+
+    void enableOptionalCodecs(BluetoothDevice device) {
+        BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
+        if (codecConfigArray == null) {
+            return;
+        }
+
+        // Set the mandatory codec's priority to default, and remove the rest
+        for (int i = 0; i < codecConfigArray.length; i++) {
+            BluetoothCodecConfig codecConfig = codecConfigArray[i];
+            if (!codecConfig.isMandatoryCodec()) {
+                codecConfigArray[i] = null;
+            }
+        }
+
+        mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
+    }
+
+    void disableOptionalCodecs(BluetoothDevice device) {
+        BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
+        if (codecConfigArray == null) {
+            return;
+        }
+        // Set the mandatory codec's priority to highest, and remove the rest
+        for (int i = 0; i < codecConfigArray.length; i++) {
+            BluetoothCodecConfig codecConfig = codecConfigArray[i];
+            if (codecConfig.isMandatoryCodec()) {
+                codecConfig.setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST);
+            } else {
+                codecConfigArray[i] = null;
+            }
+        }
+        mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
+    }
+
+    // Assign the A2DP Source codec config priorities
+    private BluetoothCodecConfig[] assignCodecConfigPriorities() {
+        Resources resources = mContext.getResources();
+        if (resources == null) {
+            return null;
+        }
+
+        int value;
+        try {
+            value = resources.getInteger(R.integer.a2dp_source_codec_priority_sbc);
+        } catch (NotFoundException e) {
+            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+        }
+        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+                < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+            mA2dpSourceCodecPrioritySbc = value;
+        }
+
+        try {
+            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aac);
+        } catch (NotFoundException e) {
+            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+        }
+        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+                < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+            mA2dpSourceCodecPriorityAac = value;
+        }
+
+        try {
+            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx);
+        } catch (NotFoundException e) {
+            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+        }
+        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+                < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+            mA2dpSourceCodecPriorityAptx = value;
+        }
+
+        try {
+            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
+        } catch (NotFoundException e) {
+            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+        }
+        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+                < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+            mA2dpSourceCodecPriorityAptxHd = value;
+        }
+
+        try {
+            value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
+        } catch (NotFoundException e) {
+            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+        }
+        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED) && (value
+                < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
+            mA2dpSourceCodecPriorityLdac = value;
+        }
+
+        BluetoothCodecConfig codecConfig;
+        BluetoothCodecConfig[] codecConfigArray =
+                new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
+        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                mA2dpSourceCodecPrioritySbc, BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
+                .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
+                0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
+        codecConfigArray[0] = codecConfig;
+        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                mA2dpSourceCodecPriorityAac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
+                .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
+                0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
+        codecConfigArray[1] = codecConfig;
+        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                mA2dpSourceCodecPriorityAptx, BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
+                .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
+                0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
+        codecConfigArray[2] = codecConfig;
+        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                mA2dpSourceCodecPriorityAptxHd, BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
+                .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
+                0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
+        codecConfigArray[3] = codecConfig;
+        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                mA2dpSourceCodecPriorityLdac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig
+                .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */,
+                0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */);
+        codecConfigArray[4] = codecConfig;
+
+        return codecConfigArray;
+    }
+}
+
diff --git a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
new file mode 100644
index 0000000..e01b325
--- /dev/null
+++ b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2017 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 the native inteface that is used by state machine/service to
+ * send or receive messages from the native stack. This file is registered
+ * for the native methods in the corresponding JNI C++ file.
+ */
+package com.android.bluetooth.a2dp;
+
+import android.bluetooth.BluetoothAdapter;
+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;
+
+/**
+ * A2DP Native Interface to/from JNI.
+ */
+public class A2dpNativeInterface {
+    private static final String TAG = "A2dpNativeInterface";
+    private static final boolean DBG = true;
+    private BluetoothAdapter mAdapter;
+
+    @GuardedBy("INSTANCE_LOCK")
+    private static A2dpNativeInterface sInstance;
+    private static final Object INSTANCE_LOCK = new Object();
+
+    static {
+        classInitNative();
+    }
+
+    @VisibleForTesting
+    private A2dpNativeInterface() {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mAdapter == null) {
+            Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+        }
+    }
+
+    /**
+     * Get singleton instance.
+     */
+    public static A2dpNativeInterface getInstance() {
+        synchronized (INSTANCE_LOCK) {
+            if (sInstance == null) {
+                sInstance = new A2dpNativeInterface();
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Initializes the native interface.
+     *
+     * @param maxConnectedAudioDevices maximum number of A2DP Sink devices that can be connected
+     * simultaneously
+     * @param codecConfigPriorities an array with the codec configuration
+     * priorities to configure.
+     */
+    public void init(int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities) {
+        initNative(maxConnectedAudioDevices, codecConfigPriorities);
+    }
+
+    /**
+     * Cleanup the native interface.
+     */
+    public void cleanup() {
+        cleanupNative();
+    }
+
+    /**
+     * Initiates A2DP connection to a remote device.
+     *
+     * @param device the remote device
+     * @return true on success, otherwise false.
+     */
+    public boolean connectA2dp(BluetoothDevice device) {
+        return connectA2dpNative(getByteAddress(device));
+    }
+
+    /**
+     * Disconnects A2DP from a remote device.
+     *
+     * @param device the remote device
+     * @return true on success, otherwise false.
+     */
+    public boolean disconnectA2dp(BluetoothDevice device) {
+        return disconnectA2dpNative(getByteAddress(device));
+    }
+
+    /**
+     * Sets a connected A2DP remote device as active.
+     *
+     * @param device the remote device
+     * @return true on success, otherwise false.
+     */
+    public boolean setActiveDevice(BluetoothDevice device) {
+        return setActiveDeviceNative(getByteAddress(device));
+    }
+
+    /**
+     * Sets the codec configuration preferences.
+     *
+     * @param device the remote Bluetooth device
+     * @param codecConfigArray an array with the codec configurations to
+     * configure.
+     * @return true on success, otherwise false.
+     */
+    public boolean setCodecConfigPreference(BluetoothDevice device,
+                                            BluetoothCodecConfig[] codecConfigArray) {
+        return setCodecConfigPreferenceNative(getByteAddress(device),
+                                              codecConfigArray);
+    }
+
+    private BluetoothDevice getDevice(byte[] address) {
+        return mAdapter.getRemoteDevice(address);
+    }
+
+    private byte[] getByteAddress(BluetoothDevice device) {
+        if (device == null) {
+            return Utils.getBytesFromAddress("00:00:00:00:00:00");
+        }
+        return Utils.getBytesFromAddress(device.getAddress());
+    }
+
+    private void sendMessageToService(A2dpStackEvent event) {
+        A2dpService service = A2dpService.getA2dpService();
+        if (service != null) {
+            service.messageFromNative(event);
+        } else {
+            Log.w(TAG, "Event ignored, service not available: " + event);
+        }
+    }
+
+    // Callbacks from the native stack back into the Java framework.
+    // All callbacks are routed via the Service which will disambiguate which
+    // state machine the message should be routed to.
+
+    private void onConnectionStateChanged(byte[] address, int state) {
+        A2dpStackEvent event =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.device = getDevice(address);
+        event.valueInt = state;
+
+        if (DBG) {
+            Log.d(TAG, "onConnectionStateChanged: " + event);
+        }
+        sendMessageToService(event);
+    }
+
+    private void onAudioStateChanged(byte[] address, int state) {
+        A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+        event.device = getDevice(address);
+        event.valueInt = state;
+
+        if (DBG) {
+            Log.d(TAG, "onAudioStateChanged: " + event);
+        }
+        sendMessageToService(event);
+    }
+
+    private void onCodecConfigChanged(byte[] address,
+            BluetoothCodecConfig newCodecConfig,
+            BluetoothCodecConfig[] codecsLocalCapabilities,
+            BluetoothCodecConfig[] codecsSelectableCapabilities) {
+        A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED);
+        event.device = getDevice(address);
+        event.codecStatus = new BluetoothCodecStatus(newCodecConfig,
+                                                     codecsLocalCapabilities,
+                                                     codecsSelectableCapabilities);
+        if (DBG) {
+            Log.d(TAG, "onCodecConfigChanged: " + event);
+        }
+        sendMessageToService(event);
+    }
+
+    // Native methods that call into the JNI interface
+    private static native void classInitNative();
+    private native void initNative(int maxConnectedAudioDevices,
+                                   BluetoothCodecConfig[] codecConfigPriorities);
+    private native void cleanupNative();
+    private native boolean connectA2dpNative(byte[] address);
+    private native boolean disconnectA2dpNative(byte[] address);
+    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
old mode 100755
new mode 100644
index bdab866..0548cdd
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -17,6 +17,7 @@
 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;
@@ -27,229 +28,560 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.ParcelUuid;
+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 com.android.bluetooth.avrcp.Avrcp;
-import com.android.bluetooth.btservice.ProfileService;
+
+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 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;
 
 /**
  * Provides Bluetooth A2DP profile, as a service in the Bluetooth application.
  * @hide
  */
 public class A2dpService extends ProfileService {
-    private static final boolean DBG = false;
-    private static final String TAG="A2dpService";
+    private static final boolean DBG = true;
+    private static final String TAG = "A2dpService";
 
-    private A2dpStateMachine mStateMachine;
+    private static A2dpService sA2dpService;
+
+    private BluetoothAdapter mAdapter;
+    private AdapterService mAdapterService;
+    private HandlerThread mStateMachinesThread;
     private Avrcp mAvrcp;
 
-    private BroadcastReceiver mConnectionStateChangedReceiver = null;
+    @VisibleForTesting
+    A2dpNativeInterface mA2dpNativeInterface;
+    private AudioManager mAudioManager;
+    private A2dpCodecConfig mA2dpCodecConfig;
 
-    private class CodecSupportReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
-                return;
-            }
-            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            if (state != BluetoothProfile.STATE_CONNECTED || device == null) {
-                return;
-            }
-            // 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.
-            int previousSupport = getSupportsOptionalCodecs(device);
-            boolean supportsOptional = false;
-            for (BluetoothCodecConfig config :
-                    mStateMachine.getCodecStatus().getCodecsSelectableCapabilities()) {
-                if (!config.isMandatoryCodec()) {
-                    supportsOptional = true;
-                    break;
-                }
-            }
-            if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
-                    || supportsOptional
-                            != (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
-                setSupportsOptionalCodecs(device, supportsOptional);
-            }
-            if (supportsOptional) {
-                int enabled = getOptionalCodecsEnabled(device);
-                if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
-                    enableOptionalCodecs();
-                } else if (enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED) {
-                    disableOptionalCodecs();
-                }
-            }
-        }
-    };
+    @GuardedBy("mStateMachines")
+    private BluetoothDevice mActiveDevice;
+    private final ConcurrentMap<BluetoothDevice, A2dpStateMachine> mStateMachines =
+            new ConcurrentHashMap<>();
 
-    private static A2dpService sAd2dpService;
-    static final ParcelUuid[] A2DP_SOURCE_UUID = {
-        BluetoothUuid.AudioSource
-    };
-    static final ParcelUuid[] A2DP_SOURCE_SINK_UUIDS = {
-        BluetoothUuid.AudioSource,
-        BluetoothUuid.AudioSink
-    };
+    // Upper limit of all A2DP devices: Bonded or Connected
+    private static final int MAX_A2DP_STATE_MACHINES = 50;
+    // Upper limit of all A2DP devices that are Connected or Connecting
+    private int mMaxConnectedAudioDevices = 1;
+    // A2DP Offload Enabled in platform
+    boolean mA2dpOffloadEnabled = false;
 
-    protected String getName() {
-        return TAG;
-    }
+    private BroadcastReceiver mBondStateChangedReceiver;
+    private BroadcastReceiver mConnectionStateChangedReceiver;
 
+    @Override
     protected IProfileServiceBinder initBinder() {
         return new BluetoothA2dpBinder(this);
     }
 
+    @Override
+    protected void create() {
+        Log.i(TAG, "create()");
+    }
+
+    @Override
     protected boolean start() {
+        Log.i(TAG, "start()");
+        if (sA2dpService != null) {
+            throw new IllegalStateException("start() called twice");
+        }
+
+        // Step 1: Get BluetoothAdapter, 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(),
+                "A2dpNativeInterface cannot be null when A2dpService starts");
+        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        Objects.requireNonNull(mAudioManager,
+                               "AudioManager cannot be null when A2dpService starts");
+
+        // Step 2: Get maximum number of connected audio devices
+        mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
+        Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
+
+        // Step 3: Setup AVRCP
         mAvrcp = Avrcp.make(this);
-        mStateMachine = A2dpStateMachine.make(this, this);
+
+        // Step 4: Start handler thread for state machines
+        mStateMachines.clear();
+        mStateMachinesThread = new HandlerThread("A2dpService.StateMachines");
+        mStateMachinesThread.start();
+
+        // Step 5: Setup codec config
+        mA2dpCodecConfig = new A2dpCodecConfig(this, mA2dpNativeInterface);
+
+        // Step 6: Initialize native interface
+        mA2dpNativeInterface.init(mMaxConnectedAudioDevices,
+                                  mA2dpCodecConfig.codecConfigPriorities());
+
+        // Step 7: 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
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        mBondStateChangedReceiver = new BondStateChangedReceiver();
+        registerReceiver(mBondStateChangedReceiver, filter);
+        filter = new IntentFilter();
+        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
+        registerReceiver(mConnectionStateChangedReceiver, filter);
+
+        // Step 9: Mark service as started
         setA2dpService(this);
-        if (mConnectionStateChangedReceiver == null) {
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
-            mConnectionStateChangedReceiver = new CodecSupportReceiver();
-            registerReceiver(mConnectionStateChangedReceiver, filter);
-        }
+
+        // Step 10: Clear active device
+        setActiveDevice(null);
+
         return true;
     }
 
+    @Override
     protected boolean stop() {
-        if (mStateMachine != null) {
-            mStateMachine.doQuit();
+        Log.i(TAG, "stop()");
+        if (sA2dpService == null) {
+            Log.w(TAG, "stop() called before start()");
+            return true;
         }
-        if (mAvrcp != null) {
-            mAvrcp.doQuit();
-        }
-        return true;
-    }
 
-    protected boolean cleanup() {
-        if (mConnectionStateChangedReceiver != null) {
-            unregisterReceiver(mConnectionStateChangedReceiver);
-            mConnectionStateChangedReceiver = null;
+        // Step 10: Store volume if there is an active device
+        if (mActiveDevice != null && AvrcpTargetService.get() != null) {
+            AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice);
         }
-        if (mStateMachine != null) {
-            mStateMachine.cleanup();
-            mStateMachine = null;
-        }
-        if (mAvrcp != null) {
-            mAvrcp.cleanup();
-            mAvrcp = null;
-        }
-        clearA2dpService();
-        return true;
-    }
 
-    //API Methods
+        // Step 9: Clear active device and stop playing audio
+        removeActiveDevice(true);
 
-    public static synchronized A2dpService getA2dpService(){
-        if (sAd2dpService != null && sAd2dpService.isAvailable()) {
-            if (DBG) Log.d(TAG, "getA2DPService(): returning " + sAd2dpService);
-            return sAd2dpService;
-        }
-        if (DBG)  {
-            if (sAd2dpService == null) {
-                Log.d(TAG, "getA2dpService(): service is NULL");
-            } else if (!(sAd2dpService.isAvailable())) {
-                Log.d(TAG,"getA2dpService(): service is not available");
+        // Step 8: Mark service as stopped
+        setA2dpService(null);
+
+        // Step 7: Unregister broadcast receivers
+        unregisterReceiver(mConnectionStateChangedReceiver);
+        mConnectionStateChangedReceiver = null;
+        unregisterReceiver(mBondStateChangedReceiver);
+        mBondStateChangedReceiver = null;
+
+        // Step 6: Cleanup native interface
+        mA2dpNativeInterface.cleanup();
+        mA2dpNativeInterface = null;
+
+        // Step 5: Clear codec config
+        mA2dpCodecConfig = null;
+
+        // Step 4: Destroy state machines and stop handler thread
+        synchronized (mStateMachines) {
+            for (A2dpStateMachine sm : mStateMachines.values()) {
+                sm.doQuit();
+                sm.cleanup();
             }
+            mStateMachines.clear();
         }
-        return null;
+        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
+        mAudioManager = null;
+        mA2dpNativeInterface = null;
+        mAdapterService = null;
+        mAdapter = null;
+
+        return true;
+    }
+
+    @Override
+    protected void cleanup() {
+        Log.i(TAG, "cleanup()");
+    }
+
+    public static synchronized A2dpService getA2dpService() {
+        if (sA2dpService == null) {
+            Log.w(TAG, "getA2dpService(): service is null");
+            return null;
+        }
+        if (!sA2dpService.isAvailable()) {
+            Log.w(TAG, "getA2dpService(): service is not available");
+            return null;
+        }
+        return sA2dpService;
     }
 
     private static synchronized void setA2dpService(A2dpService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) Log.d(TAG, "setA2dpService(): set to: " + sAd2dpService);
-            sAd2dpService = instance;
-        } else {
-            if (DBG)  {
-                if (sAd2dpService == null) {
-                    Log.d(TAG, "setA2dpService(): service not available");
-                } else if (!sAd2dpService.isAvailable()) {
-                    Log.d(TAG,"setA2dpService(): service is cleaning up");
-                }
-            }
+        if (DBG) {
+            Log.d(TAG, "setA2dpService(): set to: " + instance);
         }
-    }
-
-    private static synchronized void clearA2dpService() {
-        sAd2dpService = null;
+        sA2dpService = instance;
     }
 
     public boolean connect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "connect(): " + device);
+        }
 
         if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
+            Log.e(TAG, "Cannot connect to " + device + " : PRIORITY_OFF");
             return false;
         }
-        ParcelUuid[] featureUuids = device.getUuids();
-        if ((BluetoothUuid.containsAnyUuid(featureUuids, A2DP_SOURCE_UUID)) &&
-            !(BluetoothUuid.containsAllUuids(featureUuids ,A2DP_SOURCE_SINK_UUIDS))) {
-            Log.e(TAG,"Remote does not have A2dp Sink UUID");
+        if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
+                                         BluetoothUuid.AudioSink)) {
+            Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Sink UUID");
             return false;
         }
 
-        int connectionState = mStateMachine.getConnectionState(device);
-        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
-            connectionState == BluetoothProfile.STATE_CONNECTING) {
-            return false;
+        synchronized (mStateMachines) {
+            if (!connectionAllowedCheckMaxDevices(device)) {
+                Log.e(TAG, "Cannot connect to " + device + " : too many connected devices");
+                return false;
+            }
+            A2dpStateMachine smConnect = getOrCreateStateMachine(device);
+            if (smConnect == null) {
+                Log.e(TAG, "Cannot connect to " + device + " : no state machine");
+                return false;
+            }
+            smConnect.sendMessage(A2dpStateMachine.CONNECT);
+            return true;
         }
-
-        mStateMachine.sendMessage(A2dpStateMachine.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;
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "disconnect(): " + device);
         }
 
-        mStateMachine.sendMessage(A2dpStateMachine.DISCONNECT, device);
-        return true;
+        synchronized (mStateMachines) {
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine");
+                return false;
+            }
+            sm.sendMessage(A2dpStateMachine.DISCONNECT);
+            return true;
+        }
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getConnectedDevices();
+        synchronized (mStateMachines) {
+            List<BluetoothDevice> devices = new ArrayList<>();
+            for (A2dpStateMachine sm : mStateMachines.values()) {
+                if (sm.isConnected()) {
+                    devices.add(sm.getDevice());
+                }
+            }
+            return devices;
+        }
+    }
+
+    /**
+     * Check whether can connect to a peer device.
+     * The check considers the maximum number of connected peers.
+     *
+     * @param device the peer device to connect to
+     * @return true if connection is allowed, otherwise false
+     */
+    private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) {
+        int connected = 0;
+        // Count devices that are in the process of connecting or already connected
+        synchronized (mStateMachines) {
+            for (A2dpStateMachine sm : mStateMachines.values()) {
+                switch (sm.getConnectionState()) {
+                    case BluetoothProfile.STATE_CONNECTING:
+                    case BluetoothProfile.STATE_CONNECTED:
+                        if (Objects.equals(device, sm.getDevice())) {
+                            return true;    // Already connected or accounted for
+                        }
+                        connected++;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+        return (connected < mMaxConnectedAudioDevices);
+    }
+
+    /**
+     * 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
+     * @param isOutgoingRequest if true, the check is for outgoing connection
+     * request, otherwise is for incoming connection request
+     * @return true if connection is allowed, otherwise false
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    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.
+        if (mAdapterService.isQuietModeEnabled() && !isOutgoingRequest) {
+            Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
+            return false;
+        }
+        // Check if too many devices
+        if (!connectionAllowedCheckMaxDevices(device)) {
+            Log.e(TAG, "okToConnect: cannot connect to " + device
+                    + " : too many connected devices");
+            return false;
+        }
+        // Check priority and accept or reject the connection.
+        int priority = getPriority(device);
+        int bondState = mAdapterService.getBondState(device);
+        // Allow this connection only if the device is bonded. Any attempt to connect while
+        // bonding would potentially lead to an unauthorized connection.
+        if (bondState != BluetoothDevice.BOND_BONDED) {
+            Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
+            return false;
+        } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED
+                && priority != BluetoothProfile.PRIORITY_ON
+                && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+            // Otherwise, reject the connection if priority is not valid.
+            Log.w(TAG, "okToConnect: return false, priority=" + priority);
+            return false;
+        }
+        return true;
     }
 
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getDevicesMatchingConnectionStates(states);
+        List<BluetoothDevice> devices = new ArrayList<>();
+        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        synchronized (mStateMachines) {
+            for (BluetoothDevice device : bondedDevices) {
+                if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
+                                                 BluetoothUuid.AudioSink)) {
+                    continue;
+                }
+                int connectionState = BluetoothProfile.STATE_DISCONNECTED;
+                A2dpStateMachine sm = mStateMachines.get(device);
+                if (sm != null) {
+                    connectionState = sm.getConnectionState();
+                }
+                for (int i = 0; i < states.length; i++) {
+                    if (connectionState == states[i]) {
+                        devices.add(device);
+                    }
+                }
+            }
+            return devices;
+        }
+    }
+
+    /**
+     * Get the list of devices that have state machines.
+     *
+     * @return the list of devices that have state machines
+     */
+    @VisibleForTesting
+    List<BluetoothDevice> getDevices() {
+        List<BluetoothDevice> devices = new ArrayList<>();
+        synchronized (mStateMachines) {
+            for (A2dpStateMachine sm : mStateMachines.values()) {
+                devices.add(sm.getDevice());
+            }
+            return devices;
+        }
     }
 
     public int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getConnectionState(device);
+        synchronized (mStateMachines) {
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+            return sm.getConnectionState();
+        }
+    }
+
+    private void removeActiveDevice(boolean forceStopPlayingAudio) {
+        BluetoothDevice previousActiveDevice = mActiveDevice;
+        synchronized (mStateMachines) {
+            // Clear the active device
+            mActiveDevice = null;
+            // This needs to happen before we inform the audio manager that the device
+            // disconnected. Please see comment in broadcastActiveDevice() for why.
+            broadcastActiveDevice(null);
+
+            if (previousActiveDevice == null) {
+                return;
+            }
+
+            // Make sure the Audio Manager knows the previous Active device is disconnected.
+            // However, if A2DP is still connected and not forcing stop audio for that remote
+            // device, the user has explicitly switched the output to the local device and music
+            // should continue playing. Otherwise, the remote device has been indeed disconnected
+            // and audio should be suspended before switching the output to the local device.
+            boolean suppressNoisyIntent = !forceStopPlayingAudio
+                    && (getConnectionState(previousActiveDevice)
+                    == BluetoothProfile.STATE_CONNECTED);
+            Log.i(TAG, "removeActiveDevice: suppressNoisyIntent=" + suppressNoisyIntent);
+            mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                    previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
+                    BluetoothProfile.A2DP, suppressNoisyIntent, -1);
+            // Make sure the Active device in native layer is set to null and audio is off
+            if (!mA2dpNativeInterface.setActiveDevice(null)) {
+                Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
+                        + "layer");
+            }
+        }
+    }
+
+    /**
+     * Set the active device.
+     *
+     * @param device the active device
+     * @return true on success, otherwise false
+     */
+    public boolean setActiveDevice(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        synchronized (mStateMachines) {
+            BluetoothDevice previousActiveDevice = mActiveDevice;
+            if (DBG) {
+                Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
+            }
+
+            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);
+                return true;
+            }
+
+            BluetoothCodecStatus codecStatus = null;
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
+                          + "no state machine");
+                return false;
+            }
+            if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
+                Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
+                          + "device is not connected");
+                return false;
+            }
+            if (!mA2dpNativeInterface.setActiveDevice(device)) {
+                Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
+                return false;
+            }
+            codecStatus = sm.getCodecStatus();
+
+            boolean deviceChanged = !Objects.equals(device, mActiveDevice);
+            mActiveDevice = device;
+            // This needs to happen before we inform the audio manager that the device
+            // disconnected. Please see comment in broadcastActiveDevice() for why.
+            broadcastActiveDevice(mActiveDevice);
+            if (deviceChanged) {
+                // Send an intent with the active device codec config
+                if (codecStatus != null) {
+                    broadcastCodecConfig(mActiveDevice, codecStatus);
+                }
+                // Make sure the Audio Manager knows the previous Active device is disconnected,
+                // and the new Active device is connected.
+                // Also, mute and unmute the output during the switch to avoid audio glitches.
+                boolean wasMuted = false;
+                if (previousActiveDevice != null) {
+                    if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
+                        mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+                                                         AudioManager.ADJUST_MUTE, 0);
+                        wasMuted = true;
+                    }
+                    mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                            previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
+                            BluetoothProfile.A2DP, true, -1);
+                }
+
+                int rememberedVolume = -1;
+                if (AvrcpTargetService.get() != null) {
+                    AvrcpTargetService.get().volumeDeviceSwitched(mActiveDevice);
+
+                    rememberedVolume = AvrcpTargetService.get()
+                            .getRememberedVolumeForDevice(mActiveDevice);
+                }
+
+                mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                        mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
+                        true, rememberedVolume);
+
+                // Inform the Audio Service about the codec configuration
+                // change, so the Audio Service can reset accordingly the audio
+                // feeding parameters in the Audio HAL to the Bluetooth stack.
+                mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice);
+                if (wasMuted) {
+                    mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+                                                     AudioManager.ADJUST_UNMUTE, 0);
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Get the active device.
+     *
+     * @return the active device or null if no device is active
+     */
+    public BluetoothDevice getActiveDevice() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        synchronized (mStateMachines) {
+            return mActiveDevice;
+        }
+    }
+
+    private boolean isActiveDevice(BluetoothDevice device) {
+        synchronized (mStateMachines) {
+            return (device != null) && Objects.equals(device, mActiveDevice);
+        }
     }
 
     public boolean setPriority(BluetoothDevice device, int priority) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH_ADMIN permission");
+        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);
+                Settings.Global.getBluetoothA2dpSinkPriorityKey(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");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         int priority = Settings.Global.getInt(getContentResolver(),
-            Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
-            BluetoothProfile.PRIORITY_UNDEFINED);
+                Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
+                BluetoothProfile.PRIORITY_UNDEFINED);
         return priority;
     }
 
@@ -258,11 +590,14 @@
         return mAvrcp.isAbsoluteVolumeSupported();
     }
 
-    public void adjustAvrcpAbsoluteVolume(int direction) {
-        mAvrcp.adjustVolume(direction);
-    }
-
     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);
+            return;
+        }
+
         mAvrcp.setAbsoluteVolume(volume);
     }
 
@@ -276,35 +611,115 @@
         }
     }
 
-    synchronized boolean isA2dpPlaying(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM,
-                                       "Need BLUETOOTH permission");
-        if (DBG) Log.d(TAG, "isA2dpPlaying(" + device + ")");
-        return mStateMachine.isPlaying(device);
+    boolean isA2dpPlaying(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (DBG) {
+            Log.d(TAG, "isA2dpPlaying(" + device + ")");
+        }
+        synchronized (mStateMachines) {
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                return false;
+            }
+            return sm.isPlaying();
+        }
     }
 
-    public BluetoothCodecStatus getCodecStatus() {
+    /**
+     * Gets the current codec status (configuration and capability).
+     *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
+     * @return the current codec status
+     * @hide
+     */
+    public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (DBG) Log.d(TAG, "getCodecStatus()");
-        return mStateMachine.getCodecStatus();
+        if (DBG) {
+            Log.d(TAG, "getCodecStatus(" + device + ")");
+        }
+        synchronized (mStateMachines) {
+            if (device == null) {
+                device = mActiveDevice;
+            }
+            if (device == null) {
+                return null;
+            }
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm != null) {
+                return sm.getCodecStatus();
+            }
+            return null;
+        }
     }
 
-    public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
+    /**
+     * Sets the codec configuration preference.
+     *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
+     * @param codecConfig the codec configuration preference
+     * @hide
+     */
+    public void setCodecConfigPreference(BluetoothDevice device,
+                                         BluetoothCodecConfig codecConfig) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (DBG) Log.d(TAG, "setCodecConfigPreference(): " + Objects.toString(codecConfig));
-        mStateMachine.setCodecConfigPreference(codecConfig);
+        if (DBG) {
+            Log.d(TAG, "setCodecConfigPreference(" + device + "): "
+                    + Objects.toString(codecConfig));
+        }
+        if (device == null) {
+            device = mActiveDevice;
+        }
+        if (device == null) {
+            Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
+            return;
+        }
+        mA2dpCodecConfig.setCodecConfigPreference(device, codecConfig);
     }
 
-    public void enableOptionalCodecs() {
+    /**
+     * Enables the optional codecs.
+     *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
+     * @hide
+     */
+    public void enableOptionalCodecs(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (DBG) Log.d(TAG, "enableOptionalCodecs()");
-        mStateMachine.enableOptionalCodecs();
+        if (DBG) {
+            Log.d(TAG, "enableOptionalCodecs(" + device + ")");
+        }
+        if (device == null) {
+            device = mActiveDevice;
+        }
+        if (device == null) {
+            Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
+            return;
+        }
+        mA2dpCodecConfig.enableOptionalCodecs(device);
     }
 
-    public void disableOptionalCodecs() {
+    /**
+     * Disables the optional codecs.
+     *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
+     * @hide
+     */
+    public void disableOptionalCodecs(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (DBG) Log.d(TAG, "disableOptionalCodecs()");
-        mStateMachine.disableOptionalCodecs();
+        if (DBG) {
+            Log.d(TAG, "disableOptionalCodecs(" + device + ")");
+        }
+        if (device == null) {
+            device = mActiveDevice;
+        }
+        if (device == null) {
+            Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
+            return;
+        }
+        mA2dpCodecConfig.disableOptionalCodecs(device);
     }
 
     public int getSupportsOptionalCodecs(BluetoothDevice device) {
@@ -318,7 +733,7 @@
     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;
+                : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
         Settings.Global.putInt(getContentResolver(),
                 Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
                 value);
@@ -344,14 +759,264 @@
                 value);
     }
 
-    //Binder object: Must be static class or memory leak may occur 
-    private static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub 
-        implements IProfileServiceBinder {
+    // Handle messages from native (JNI) to Java
+    void messageFromNative(A2dpStackEvent stackEvent) {
+        Objects.requireNonNull(stackEvent.device,
+                               "Device should never be null, event: " + stackEvent);
+        synchronized (mStateMachines) {
+            BluetoothDevice device = stackEvent.device;
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                if (stackEvent.type == A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
+                    switch (stackEvent.valueInt) {
+                        case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
+                        case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
+                            // Create a new state machine only when connecting to a device
+                            if (!connectionAllowedCheckMaxDevices(device)) {
+                                Log.e(TAG, "Cannot connect to " + device
+                                        + " : too many connected devices");
+                                return;
+                            }
+                            sm = getOrCreateStateMachine(device);
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            }
+            if (sm == null) {
+                Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
+                return;
+            }
+            sm.sendMessage(A2dpStateMachine.STACK_EVENT, stackEvent);
+        }
+    }
+
+    /**
+     * The codec configuration for a device has been updated.
+     *
+     * @param device the remote device
+     * @param codecStatus the new codec status
+     * @param sameAudioFeedingParameters if true the audio feeding parameters
+     * haven't been changed
+     */
+    void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
+                            boolean sameAudioFeedingParameters) {
+        broadcastCodecConfig(device, codecStatus);
+
+        // Inform the Audio Service about the codec configuration change,
+        // so the Audio Service can reset accordingly the audio feeding
+        // parameters in the Audio HAL to the Bluetooth stack.
+        if (isActiveDevice(device) && !sameAudioFeedingParameters) {
+            mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
+        }
+    }
+
+    private A2dpStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+        if (device == null) {
+            Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
+            return null;
+        }
+        synchronized (mStateMachines) {
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm != null) {
+                return sm;
+            }
+            // Limit the maximum number of state machines to avoid DoS attack
+            if (mStateMachines.size() >= MAX_A2DP_STATE_MACHINES) {
+                Log.e(TAG, "Maximum number of A2DP state machines reached: "
+                        + MAX_A2DP_STATE_MACHINES);
+                return null;
+            }
+            if (DBG) {
+                Log.d(TAG, "Creating a new state machine for " + device);
+            }
+            sm = A2dpStateMachine.make(device, this, mA2dpNativeInterface,
+                                       mStateMachinesThread.getLooper());
+            mStateMachines.put(device, sm);
+            return sm;
+        }
+    }
+
+    private void broadcastActiveDevice(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "broadcastActiveDevice(" + 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
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
+
+    private void broadcastCodecConfig(BluetoothDevice device, BluetoothCodecStatus codecStatus) {
+        if (DBG) {
+            Log.d(TAG, "broadcastCodecConfig(" + device + "): " + codecStatus);
+        }
+        Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
+        intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, codecStatus);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
+    }
+
+    private class BondStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            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);
+        }
+    }
+
+    /**
+     * Process a change in the bonding state for a device.
+     *
+     * @param device the device whose bonding state has changed
+     * @param bondState the new bond state for the device. Possible values are:
+     * {@link BluetoothDevice#BOND_NONE},
+     * {@link BluetoothDevice#BOND_BONDING},
+     * {@link BluetoothDevice#BOND_BONDED}.
+     */
+    @VisibleForTesting
+    void bondStateChanged(BluetoothDevice device, int bondState) {
+        if (DBG) {
+            Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState);
+        }
+        // Remove state machine if the bonding for a device is removed
+        if (bondState != BluetoothDevice.BOND_NONE) {
+            return;
+        }
+        synchronized (mStateMachines) {
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                return;
+            }
+            if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
+                return;
+            }
+            removeStateMachine(device);
+        }
+    }
+
+    private void removeStateMachine(BluetoothDevice device) {
+        synchronized (mStateMachines) {
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                Log.w(TAG, "removeStateMachine: device " + device
+                        + " does not have a state machine");
+                return;
+            }
+            Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
+            sm.doQuit();
+            sm.cleanup();
+            mStateMachines.remove(device);
+        }
+    }
+
+    private void updateOptionalCodecsSupport(BluetoothDevice device) {
+        int previousSupport = getSupportsOptionalCodecs(device);
+        boolean supportsOptional = false;
+
+        synchronized (mStateMachines) {
+            A2dpStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                return;
+            }
+            BluetoothCodecStatus codecStatus = sm.getCodecStatus();
+            if (codecStatus != null) {
+                for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
+                    if (!config.isMandatoryCodec()) {
+                        supportsOptional = true;
+                        break;
+                    }
+                }
+            }
+        }
+        if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
+                || supportsOptional != (previousSupport
+                                    == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
+            setSupportsOptionalCodecs(device, supportsOptional);
+        }
+        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);
+            }
+        }
+    }
+
+    private void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
+        if ((device == null) || (fromState == toState)) {
+            return;
+        }
+        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
+            if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) {
+                setActiveDevice(device);
+            }
+            // Check if the active device is not connected anymore
+            if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) {
+                setActiveDevice(null);
+            }
+            // Check if the device is disconnected - if unbond, remove the state machine
+            if (toState == BluetoothProfile.STATE_DISCONNECTED) {
+                int bondState = mAdapterService.getBondState(device);
+                if (bondState == BluetoothDevice.BOND_NONE) {
+                    removeStateMachine(device);
+                }
+            }
+        }
+    }
+
+    /**
+     * Receiver for processing device connection state changes.
+     *
+     * <ul>
+     * <li> Update codec support per device when device is (re)connected
+     * <li> Delete the state machine instance if the device is disconnected and unbond
+     * </ul>
+     */
+    private class ConnectionStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+            int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+            connectionStateChanged(device, fromState, toState);
+        }
+    }
+
+    /**
+     * Binder object: must be a static class or memory leak may occur.
+     */
+    @VisibleForTesting
+    static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub
+            implements IProfileServiceBinder {
         private A2dpService mService;
 
         private A2dpService getService() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"A2dp call not allowed for non-active user");
+                Log.w(TAG, "A2DP call not allowed for non-active user");
                 return null;
             }
 
@@ -365,125 +1030,187 @@
             mService = svc;
         }
 
-        public boolean cleanup()  {
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
+        @Override
         public boolean connect(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.connect(device);
         }
 
+        @Override
         public boolean disconnect(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.disconnect(device);
         }
 
+        @Override
         public List<BluetoothDevice> getConnectedDevices() {
             A2dpService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<>(0);
+            }
             return service.getConnectedDevices();
         }
 
+        @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
             A2dpService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<>(0);
+            }
             return service.getDevicesMatchingConnectionStates(states);
         }
 
+        @Override
         public int getConnectionState(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
             return service.getConnectionState(device);
         }
 
+        @Override
+        public boolean setActiveDevice(BluetoothDevice device) {
+            A2dpService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setActiveDevice(device);
+        }
+
+        @Override
+        public BluetoothDevice getActiveDevice() {
+            A2dpService service = getService();
+            if (service == null) {
+                return null;
+            }
+            return service.getActiveDevice();
+        }
+
+        @Override
         public boolean setPriority(BluetoothDevice device, int priority) {
             A2dpService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPriority(device, priority);
         }
 
+        @Override
         public int getPriority(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
+            if (service == null) {
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
             return service.getPriority(device);
         }
 
+        @Override
         public boolean isAvrcpAbsoluteVolumeSupported() {
             A2dpService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isAvrcpAbsoluteVolumeSupported();
         }
 
-        public void adjustAvrcpAbsoluteVolume(int direction) {
-            A2dpService service = getService();
-            if (service == null) return;
-            service.adjustAvrcpAbsoluteVolume(direction);
-        }
-
+        @Override
         public void setAvrcpAbsoluteVolume(int volume) {
             A2dpService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setAvrcpAbsoluteVolume(volume);
         }
 
+        @Override
         public boolean isA2dpPlaying(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isA2dpPlaying(device);
         }
 
-        public BluetoothCodecStatus getCodecStatus() {
+        @Override
+        public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return null;
-            return service.getCodecStatus();
+            if (service == null) {
+                return null;
+            }
+            return service.getCodecStatus(device);
         }
 
-        public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
+        @Override
+        public void setCodecConfigPreference(BluetoothDevice device,
+                                             BluetoothCodecConfig codecConfig) {
             A2dpService service = getService();
-            if (service == null) return;
-            service.setCodecConfigPreference(codecConfig);
+            if (service == null) {
+                return;
+            }
+            service.setCodecConfigPreference(device, codecConfig);
         }
 
-        public void enableOptionalCodecs() {
+        @Override
+        public void enableOptionalCodecs(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return;
-            service.enableOptionalCodecs();
+            if (service == null) {
+                return;
+            }
+            service.enableOptionalCodecs(device);
         }
 
-        public void disableOptionalCodecs() {
+        @Override
+        public void disableOptionalCodecs(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return;
-            service.disableOptionalCodecs();
+            if (service == null) {
+                return;
+            }
+            service.disableOptionalCodecs(device);
         }
 
         public int supportsOptionalCodecs(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+            if (service == null) {
+                return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+            }
             return service.getSupportsOptionalCodecs(device);
         }
 
         public int getOptionalCodecsEnabled(BluetoothDevice device) {
             A2dpService service = getService();
-            if (service == null) return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+            if (service == null) {
+                return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+            }
             return service.getOptionalCodecsEnabled(device);
         }
 
         public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
             A2dpService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setOptionalCodecsEnabled(device, value);
         }
-    };
+    }
 
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
-        if (mStateMachine != null) {
-            mStateMachine.dump(sb);
+        ProfileService.println(sb, "mActiveDevice: " + mActiveDevice);
+        for (A2dpStateMachine sm : mStateMachines.values()) {
+            sm.dump(sb);
         }
         if (mAvrcp != null) {
             mAvrcp.dump(sb);
diff --git a/src/com/android/bluetooth/a2dp/A2dpStackEvent.java b/src/com/android/bluetooth/a2dp/A2dpStackEvent.java
new file mode 100644
index 0000000..4ebd82a
--- /dev/null
+++ b/src/com/android/bluetooth/a2dp/A2dpStackEvent.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.a2dp;
+
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * Stack event sent via a callback from JNI to Java, or generated
+ * internally by the A2DP State Machine.
+ */
+public class A2dpStackEvent {
+    // Event types for STACK_EVENT message (coming from native)
+    private static final int EVENT_TYPE_NONE = 0;
+    public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+    public static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+    public static final int EVENT_TYPE_CODEC_CONFIG_CHANGED = 3;
+
+    // Do not modify without updating the HAL bt_av.h files.
+    // Match up with btav_connection_state_t enum of bt_av.h
+    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;
+
+    public int type = EVENT_TYPE_NONE;
+    public BluetoothDevice device;
+    public int valueInt = 0;
+    public BluetoothCodecStatus codecStatus;
+
+    A2dpStackEvent(int type) {
+        this.type = type;
+    }
+
+    @Override
+    public String toString() {
+        // event dump
+        StringBuilder result = new StringBuilder();
+        result.append("A2dpStackEvent {type:" + eventTypeToString(type));
+        result.append(", device:" + device);
+        result.append(", value1:" + eventTypeValueIntToString(type, valueInt));
+        if (codecStatus != null) {
+            result.append(", codecStatus:" + codecStatus);
+        }
+        result.append("}");
+        return result.toString();
+    }
+
+    private static String eventTypeToString(int type) {
+        switch (type) {
+            case EVENT_TYPE_NONE:
+                return "EVENT_TYPE_NONE";
+            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                return "EVENT_TYPE_CONNECTION_STATE_CHANGED";
+            case EVENT_TYPE_AUDIO_STATE_CHANGED:
+                return "EVENT_TYPE_AUDIO_STATE_CHANGED";
+            case EVENT_TYPE_CODEC_CONFIG_CHANGED:
+                return "EVENT_TYPE_CODEC_CONFIG_CHANGED";
+            default:
+                return "EVENT_TYPE_UNKNOWN:" + type;
+        }
+    }
+
+    private static String eventTypeValueIntToString(int type, int value) {
+        switch (type) {
+            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                switch (value) {
+                    case CONNECTION_STATE_DISCONNECTED:
+                        return "DISCONNECTED";
+                    case CONNECTION_STATE_CONNECTING:
+                        return "CONNECTING";
+                    case CONNECTION_STATE_CONNECTED:
+                        return "CONNECTED";
+                    case CONNECTION_STATE_DISCONNECTING:
+                        return "DISCONNECTING";
+                    default:
+                        break;
+                }
+                break;
+            case EVENT_TYPE_AUDIO_STATE_CHANGED:
+                switch (value) {
+                    case AUDIO_STATE_REMOTE_SUSPEND:
+                        return "REMOTE_SUSPEND";
+                    case AUDIO_STATE_STOPPED:
+                        return "STOPPED";
+                    case AUDIO_STATE_STARTED:
+                        return "STARTED";
+                    default:
+                        break;
+                }
+                break;
+            default:
+                break;
+        }
+        return Integer.toString(value);
+    }
+}
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
old mode 100755
new mode 100644
index 7a18989..3ab26e1
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -15,773 +15,593 @@
  */
 
 /**
- * Bluetooth A2dp StateMachine
- *                      (Disconnected)
- *                           |    ^
- *                   CONNECT |    | DISCONNECTED
- *                           V    |
- *                         (Pending)
- *                           |    ^
- *                 CONNECTED |    | CONNECT
- *                           V    |
- *                        (Connected)
+ * Bluetooth A2DP StateMachine. There is one instance per remote device.
+ *  - "Disconnected" and "Connected" are steady states.
+ *  - "Connecting" and "Disconnecting" are transient states until the
+ *     connection / disconnection is completed.
+ *
+ *
+ *                        (Disconnected)
+ *                           |       ^
+ *                   CONNECT |       | DISCONNECTED
+ *                           V       |
+ *                 (Connecting)<--->(Disconnecting)
+ *                           |       ^
+ *                 CONNECTED |       | DISCONNECT
+ *                           V       |
+ *                          (Connected)
+ * NOTES:
+ *  - If state machine is in "Connecting" state and the remote device sends
+ *    DISCONNECT request, the state machine transitions to "Disconnecting" state.
+ *  - Similarly, if the state machine is in "Disconnecting" state and the remote device
+ *    sends CONNECT request, the state machine transitions to "Connecting" state.
+ *
+ *                    DISCONNECT
+ *    (Connecting) ---------------> (Disconnecting)
+ *                 <---------------
+ *                      CONNECT
+ *
  */
+
 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;
 import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.media.AudioManager;
-import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
+import android.support.annotation.VisibleForTesting;
 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.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Scanner;
 
 final class A2dpStateMachine extends StateMachine {
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String TAG = "A2dpStateMachine";
 
     static final int CONNECT = 1;
     static final int DISCONNECT = 2;
-    private static final int STACK_EVENT = 101;
+    @VisibleForTesting
+    static final int STACK_EVENT = 101;
     private static final int CONNECT_TIMEOUT = 201;
 
+    // NOTE: the value is not "final" - it is modified in the unit tests
+    @VisibleForTesting
+    static int sConnectTimeoutMs = 30000;        // 30s
+
     private Disconnected mDisconnected;
-    private Pending mPending;
+    private Connecting mConnecting;
+    private Disconnecting mDisconnecting;
     private Connected mConnected;
+    private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+    private int mLastConnectionState = -1;
 
-    private A2dpService mService;
-    private Context mContext;
-    private BluetoothAdapter mAdapter;
-    private final AudioManager mAudioManager;
-    private IntentBroadcastHandler mIntentBroadcastHandler;
-    private final WakeLock mWakeLock;
-    private BluetoothCodecConfig[] mCodecConfigPriorities;
+    private A2dpService mA2dpService;
+    private A2dpNativeInterface mA2dpNativeInterface;
+    private boolean mA2dpOffloadEnabled = false;
+    private final BluetoothDevice mDevice;
+    private boolean mIsPlaying = false;
+    private BluetoothCodecStatus mCodecStatus;
 
-    private static final int MSG_CONNECTION_STATE_CHANGED = 0;
-
-    // mCurrentDevice is the device connected before the state changes
-    // mTargetDevice is the device to be connected
-    // mIncomingDevice is the device connecting to us, valid only in Pending state
-    //                when mIncomingDevice is not null, both mCurrentDevice
-    //                  and mTargetDevice are null
-    //                when either mCurrentDevice or mTargetDevice is not null,
-    //                  mIncomingDevice is null
-    // Stable states
-    //   No connection, Disconnected state
-    //                  both mCurrentDevice and mTargetDevice are null
-    //   Connected, Connected state
-    //              mCurrentDevice is not null, mTargetDevice is null
-    // Interim states
-    //   Connecting to a device, Pending
-    //                           mCurrentDevice is null, mTargetDevice is not null
-    //   Disconnecting device, Connecting to new device
-    //     Pending
-    //     Both mCurrentDevice and mTargetDevice are not null
-    //   Disconnecting device Pending
-    //                        mCurrentDevice is not null, mTargetDevice is null
-    //   Incoming connections Pending
-    //                        Both mCurrentDevice and mTargetDevice are null
-    private BluetoothDevice mCurrentDevice = null;
-    private BluetoothDevice mTargetDevice = null;
-    private BluetoothDevice mIncomingDevice = null;
-    private BluetoothDevice mPlayingA2dpDevice = null;
-
-    private BluetoothCodecStatus mCodecStatus = null;
-    private int mA2dpSourceCodecPrioritySbc = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-    private int mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-    private int mA2dpSourceCodecPriorityAptx = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-    private int mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-    private int mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-
-    static {
-        classInitNative();
-    }
-
-    private A2dpStateMachine(A2dpService svc, Context context) {
-        super("A2dpStateMachine");
-        mService = svc;
-        mContext = context;
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        mCodecConfigPriorities = assignCodecConfigPriorities();
-
-        initNative(mCodecConfigPriorities);
+    A2dpStateMachine(BluetoothDevice device, A2dpService a2dpService,
+                     A2dpNativeInterface a2dpNativeInterface, Looper looper) {
+        super(TAG, looper);
+        setDbg(DBG);
+        mDevice = device;
+        mA2dpService = a2dpService;
+        mA2dpNativeInterface = a2dpNativeInterface;
 
         mDisconnected = new Disconnected();
-        mPending = new Pending();
+        mConnecting = new Connecting();
+        mDisconnecting = new Disconnecting();
         mConnected = new Connected();
 
         addState(mDisconnected);
-        addState(mPending);
+        addState(mConnecting);
+        addState(mDisconnecting);
         addState(mConnected);
+        mA2dpOffloadEnabled = mA2dpService.mA2dpOffloadEnabled;
 
         setInitialState(mDisconnected);
-
-        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService");
-
-        mIntentBroadcastHandler = new IntentBroadcastHandler();
-
-        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
     }
 
-    static A2dpStateMachine make(A2dpService svc, Context context) {
-        Log.d(TAG, "make");
-        A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context);
+    static A2dpStateMachine make(BluetoothDevice device, A2dpService a2dpService,
+                                 A2dpNativeInterface a2dpNativeInterface, Looper looper) {
+        Log.i(TAG, "make for device " + device);
+        A2dpStateMachine a2dpSm = new A2dpStateMachine(device, a2dpService, a2dpNativeInterface,
+                                                       looper);
         a2dpSm.start();
         return a2dpSm;
     }
 
-    // Assign the A2DP Source codec config priorities
-    private BluetoothCodecConfig[] assignCodecConfigPriorities() {
-        Resources resources = mContext.getResources();
-        if (resources == null) {
-            return null;
-        }
-
-        int value;
-        try {
-            value = resources.getInteger(R.integer.a2dp_source_codec_priority_sbc);
-        } catch (NotFoundException e) {
-            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-        }
-        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
-                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
-            mA2dpSourceCodecPrioritySbc = value;
-        }
-
-        try {
-            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aac);
-        } catch (NotFoundException e) {
-            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-        }
-        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
-                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
-            mA2dpSourceCodecPriorityAac = value;
-        }
-
-        try {
-            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx);
-        } catch (NotFoundException e) {
-            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-        }
-        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
-                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
-            mA2dpSourceCodecPriorityAptx = value;
-        }
-
-        try {
-            value = resources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd);
-        } catch (NotFoundException e) {
-            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-        }
-        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
-                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
-            mA2dpSourceCodecPriorityAptxHd = value;
-        }
-
-        try {
-            value = resources.getInteger(R.integer.a2dp_source_codec_priority_ldac);
-        } catch (NotFoundException e) {
-            value = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-        }
-        if ((value >= BluetoothCodecConfig.CODEC_PRIORITY_DISABLED)
-                && (value < BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST)) {
-            mA2dpSourceCodecPriorityLdac = value;
-        }
-
-        BluetoothCodecConfig codecConfig;
-        BluetoothCodecConfig[] codecConfigArray =
-                new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
-        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
-                mA2dpSourceCodecPrioritySbc, BluetoothCodecConfig.SAMPLE_RATE_NONE,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
-                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
-                0 /* codecSpecific4 */);
-        codecConfigArray[0] = codecConfig;
-        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
-                mA2dpSourceCodecPriorityAac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
-                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
-                0 /* codecSpecific4 */);
-        codecConfigArray[1] = codecConfig;
-        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
-                mA2dpSourceCodecPriorityAptx, BluetoothCodecConfig.SAMPLE_RATE_NONE,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
-                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
-                0 /* codecSpecific4 */);
-        codecConfigArray[2] = codecConfig;
-        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
-                mA2dpSourceCodecPriorityAptxHd, BluetoothCodecConfig.SAMPLE_RATE_NONE,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
-                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
-                0 /* codecSpecific4 */);
-        codecConfigArray[3] = codecConfig;
-        codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
-                mA2dpSourceCodecPriorityLdac, BluetoothCodecConfig.SAMPLE_RATE_NONE,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig.CHANNEL_MODE_NONE,
-                0 /* codecSpecific1 */, 0 /* codecSpecific2 */, 0 /* codecSpecific3 */,
-                0 /* codecSpecific4 */);
-        codecConfigArray[4] = codecConfig;
-
-        return codecConfigArray;
-    }
-
     public void doQuit() {
-        if ((mTargetDevice != null) &&
-            (getConnectionState(mTargetDevice) == BluetoothProfile.STATE_CONNECTING)) {
-            log("doQuit()- Move A2DP State to DISCONNECTED");
-            broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                     BluetoothProfile.STATE_CONNECTING);
+        log("doQuit for device " + mDevice);
+        if (mIsPlaying) {
+            // 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);
         }
         quitNow();
     }
 
     public void cleanup() {
-        cleanupNative();
+        log("cleanup for device " + mDevice);
     }
 
-        private class Disconnected extends State {
+    @VisibleForTesting
+    class Disconnected extends State {
         @Override
         public void enter() {
-            log("Enter Disconnected: " + getCurrentMessage().what);
-            if (mCurrentDevice != null || mTargetDevice != null || mIncomingDevice != null) {
-                loge("ERROR: enter() inconsistent state in Disconnected: current = "
-                        + mCurrentDevice + " target = " + mTargetDevice + " incoming = "
-                        + mIncomingDevice);
+            Message currentMessage = getCurrentMessage();
+            Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
+                    : messageWhatToString(currentMessage.what)));
+            mConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+
+            removeDeferredMessages(DISCONNECT);
+
+            if (mLastConnectionState != -1) {
+                // Don't broadcast during startup
+                broadcastConnectionState(mConnectionState, mLastConnectionState);
+                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);
+                }
             }
         }
 
         @Override
-        public boolean processMessage(Message message) {
-            log("Disconnected process message: " + message.what);
-            if (mCurrentDevice != null || mTargetDevice != null  || mIncomingDevice != null) {
-                loge("ERROR: not null state in Disconnected: current = " + mCurrentDevice
-                        + " target = " + mTargetDevice + " incoming = " + mIncomingDevice);
-                mCurrentDevice = null;
-                mTargetDevice = null;
-                mIncomingDevice = null;
-            }
-
-            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 (A2dpStateMachine.this) {
-                        mTargetDevice = device;
-                        transitionTo(mPending);
-                    }
-                    // TODO(BT) remove CONNECT_TIMEOUT when the stack
-                    //          sends back events consistently
-                    sendMessageDelayed(CONNECT_TIMEOUT, 30000);
-                    break;
-                case DISCONNECT:
-                    // ignore
-                    break;
-                case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
-                    switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.valueInt, event.device);
-                            break;
-                        default:
-                            loge("Unexpected stack event: " + event.type);
-                            break;
-                    }
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return retValue;
-        }
-
-        @Override
         public void exit() {
-            log("Exit Disconnected: " + getCurrentMessage().what);
-        }
-
-        // in Disconnected state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
-            switch (state) {
-            case CONNECTION_STATE_DISCONNECTED:
-                logw("Ignore HF DISCONNECTED event, device: " + device);
-                break;
-            case CONNECTION_STATE_CONNECTING:
-                if (okToConnect(device)){
-                    logi("Incoming A2DP accepted");
-                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                             BluetoothProfile.STATE_DISCONNECTED);
-                    synchronized (A2dpStateMachine.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 (A2dpStateMachine.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 A2dp 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);
-            if (mTargetDevice != null && mIncomingDevice != null) {
-                loge("ERROR: enter() inconsistent state in Pending: current = " + mCurrentDevice
-                        + " target = " + mTargetDevice + " incoming = " + mIncomingDevice);
-            }
+            Message currentMessage = getCurrentMessage();
+            log("Exit Disconnected(" + mDevice + "): " + (currentMessage == null ? "null"
+                    : messageWhatToString(currentMessage.what)));
+            mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
         }
 
         @Override
         public boolean processMessage(Message message) {
-            log("Pending process message: " + message.what);
+            log("Disconnected process message(" + mDevice + "): "
+                    + messageWhatToString(message.what));
 
-            boolean retValue = HANDLED;
-            switch(message.what) {
+            switch (message.what) {
                 case CONNECT:
-                    deferMessage(message);
-                    break;
-                case CONNECT_TIMEOUT:
-                    onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED,
-                                             getByteAddress(mTargetDevice));
-                    break;
-                case DISCONNECT:
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (mCurrentDevice != null && mTargetDevice != null &&
-                        mTargetDevice.equals(device) ) {
-                        // cancel connection to the mTargetDevice
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                       BluetoothProfile.STATE_CONNECTING);
-                        synchronized (A2dpStateMachine.this) {
-                            mTargetDevice = null;
-                        }
+                    Log.i(TAG, "Connecting to " + mDevice);
+                    if (!mA2dpNativeInterface.connectA2dp(mDevice)) {
+                        Log.e(TAG, "Disconnected: error connecting to " + mDevice);
+                        break;
+                    }
+                    if (mA2dpService.okToConnect(mDevice, true)) {
+                        transitionTo(mConnecting);
                     } else {
-                        deferMessage(message);
+                        // Reject the request and stay in Disconnected state
+                        Log.w(TAG, "Outgoing A2DP Connecting request rejected: " + mDevice);
                     }
                     break;
+                case DISCONNECT:
+                    Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice);
+                    break;
                 case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
+                    A2dpStackEvent event = (A2dpStackEvent) message.obj;
+                    log("Disconnected: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
                     switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            removeMessages(CONNECT_TIMEOUT);
-                            processConnectionEvent(event.valueInt, event.device);
+                        case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt);
+                            break;
+                        case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
+                            processCodecConfigEvent(event.codecStatus);
                             break;
                         default:
-                            loge("Unexpected stack event: " + event.type);
+                            Log.e(TAG, "Disconnected: ignoring stack event: " + event);
                             break;
                     }
                     break;
                 default:
                     return NOT_HANDLED;
             }
-            return retValue;
+            return HANDLED;
         }
 
-        // in Pending state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
-            switch (state) {
-                case CONNECTION_STATE_DISCONNECTED:
-                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                        broadcastConnectionState(mCurrentDevice,
-                                                 BluetoothProfile.STATE_DISCONNECTED,
-                                                 BluetoothProfile.STATE_DISCONNECTING);
-                        synchronized (A2dpStateMachine.this) {
-                            mCurrentDevice = null;
-                        }
-
-                        if (mTargetDevice != null) {
-                            if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
-                                broadcastConnectionState(mTargetDevice,
-                                                         BluetoothProfile.STATE_DISCONNECTED,
-                                                         BluetoothProfile.STATE_CONNECTING);
-                                synchronized (A2dpStateMachine.this) {
-                                    mTargetDevice = null;
-                                    transitionTo(mDisconnected);
-                                }
-                            }
-                        } else {
-                            synchronized (A2dpStateMachine.this) {
-                                mIncomingDevice = null;
-                                transitionTo(mDisconnected);
-                            }
-                        }
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        // outgoing connection failed
-                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                                 BluetoothProfile.STATE_CONNECTING);
-                        // check if there is some incoming connection request
-                        if (mIncomingDevice != null) {
-                            logi("disconnect for outgoing in pending state");
-                            synchronized (A2dpStateMachine.this) {
-                                mTargetDevice = null;
-                            }
-                            break;
-                        }
-                        synchronized (A2dpStateMachine.this) {
-                            mTargetDevice = null;
-                            transitionTo(mDisconnected);
-                        }
-                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                        broadcastConnectionState(mIncomingDevice,
-                                                 BluetoothProfile.STATE_DISCONNECTED,
-                                                 BluetoothProfile.STATE_CONNECTING);
-                        synchronized (A2dpStateMachine.this) {
-                            mIncomingDevice = null;
-                            transitionTo(mDisconnected);
-                        }
+        // in Disconnected state
+        private void processConnectionEvent(int event) {
+            switch (event) {
+                case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.w(TAG, "Ignore A2DP DISCONNECTED event: " + mDevice);
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
+                    if (mA2dpService.okToConnect(mDevice, false)) {
+                        Log.i(TAG, "Incoming A2DP Connecting request accepted: " + mDevice);
+                        transitionTo(mConnecting);
                     } else {
-                        loge("Unknown device Disconnected: " + device);
+                        // Reject the connection and stay in Disconnected state itself
+                        Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice);
+                        mA2dpNativeInterface.disconnectA2dp(mDevice);
                     }
                     break;
-            case CONNECTION_STATE_CONNECTED:
-                if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                    // disconnection failed
-                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
-                                             BluetoothProfile.STATE_DISCONNECTING);
-                    if (mTargetDevice != null) {
-                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                                 BluetoothProfile.STATE_CONNECTING);
-                    }
-                    synchronized (A2dpStateMachine.this) {
-                        mTargetDevice = null;
+                case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
+                    Log.w(TAG, "A2DP Connected from Disconnected state: " + mDevice);
+                    if (mA2dpService.okToConnect(mDevice, false)) {
+                        Log.i(TAG, "Incoming A2DP Connected request accepted: " + mDevice);
                         transitionTo(mConnected);
-                    }
-                } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                    broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
-                                             BluetoothProfile.STATE_CONNECTING);
-                    synchronized (A2dpStateMachine.this) {
-                        mCurrentDevice = mTargetDevice;
-                        mTargetDevice = null;
-                        transitionTo(mConnected);
-                    }
-                } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                    broadcastConnectionState(mIncomingDevice, BluetoothProfile.STATE_CONNECTED,
-                                             BluetoothProfile.STATE_CONNECTING);
-                    // check for a2dp connection allowed for this device in race condition
-                    if (okToConnect(mIncomingDevice)) {
-                        logi("Ready to connect incoming Connection from pending state");
-                        synchronized (A2dpStateMachine.this) {
-                            mCurrentDevice = mIncomingDevice;
-                            mIncomingDevice = null;
-                            transitionTo(mConnected);
-                        }
                     } else {
-                        // A2dp connection unchecked for this device
-                        loge("Incoming A2DP rejected from pending state");
-                        disconnectA2dpNative(getByteAddress(device));
+                        // Reject the connection and stay in Disconnected state itself
+                        Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice);
+                        mA2dpNativeInterface.disconnectA2dp(mDevice);
                     }
-                } else {
-                    loge("Unknown device Connected: " + device);
-                    // something is wrong here, but sync our state with stack
-                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                             BluetoothProfile.STATE_DISCONNECTED);
-                    synchronized (A2dpStateMachine.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, accept it");
-                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                             BluetoothProfile.STATE_DISCONNECTED);
-                    mIncomingDevice = device;
-                }
-                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 unknow device: " + device);
-                }
-                break;
-            default:
-                loge("Incorrect state: " + state);
-                break;
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    Log.w(TAG, "Ignore A2DP DISCONNECTING event: " + mDevice);
+                    break;
+                default:
+                    Log.e(TAG, "Incorrect event: " + event + " device: " + mDevice);
+                    break;
             }
         }
-
     }
 
-    private class Connected extends State {
+    @VisibleForTesting
+    class Connecting extends State {
         @Override
         public void enter() {
-            // Remove pending connection attempts that were deferred during the pending
-            // state. This is to prevent auto connect attempts from disconnecting
-            // devices that previously successfully connected.
-            // TODO: This needs to check for multiple A2DP connections, once supported...
+            Message currentMessage = getCurrentMessage();
+            Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
+                    : messageWhatToString(currentMessage.what)));
+            sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+            mConnectionState = BluetoothProfile.STATE_CONNECTING;
+            broadcastConnectionState(mConnectionState, mLastConnectionState);
+        }
+
+        @Override
+        public void exit() {
+            Message currentMessage = getCurrentMessage();
+            log("Exit Connecting(" + mDevice + "): " + (currentMessage == null ? "null"
+                    : messageWhatToString(currentMessage.what)));
+            mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
+            removeMessages(CONNECT_TIMEOUT);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Connecting process message(" + mDevice + "): "
+                    + messageWhatToString(message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    deferMessage(message);
+                    break;
+                case CONNECT_TIMEOUT: {
+                    Log.w(TAG, "Connecting connection timeout: " + mDevice);
+                    mA2dpNativeInterface.disconnectA2dp(mDevice);
+                    A2dpStackEvent event =
+                            new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+                    event.device = mDevice;
+                    event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
+                    sendMessage(STACK_EVENT, event);
+                    break;
+                }
+                case DISCONNECT:
+                    // Cancel connection
+                    Log.i(TAG, "Connecting: connection canceled to " + mDevice);
+                    mA2dpNativeInterface.disconnectA2dp(mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case STACK_EVENT:
+                    A2dpStackEvent event = (A2dpStackEvent) message.obj;
+                    log("Connecting: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt);
+                            break;
+                        case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
+                            processCodecConfigEvent(event.codecStatus);
+                            break;
+                        case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
+                            break;
+                        default:
+                            Log.e(TAG, "Connecting: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Connecting state
+        private void processConnectionEvent(int event) {
+            switch (event) {
+                case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.w(TAG, "Connecting device disconnected: " + mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
+                    transitionTo(mConnected);
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
+                    // Ignored - probably an event that the outgoing connection was initiated
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice);
+                    transitionTo(mDisconnecting);
+                    break;
+                default:
+                    Log.e(TAG, "Incorrect event: " + event);
+                    break;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    class Disconnecting extends State {
+        @Override
+        public void enter() {
+            Message currentMessage = getCurrentMessage();
+            Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
+                    : messageWhatToString(currentMessage.what)));
+            sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+            mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+            broadcastConnectionState(mConnectionState, mLastConnectionState);
+        }
+
+        @Override
+        public void exit() {
+            Message currentMessage = getCurrentMessage();
+            log("Exit Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null"
+                    : messageWhatToString(currentMessage.what)));
+            mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+            removeMessages(CONNECT_TIMEOUT);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Disconnecting process message(" + mDevice + "): "
+                    + messageWhatToString(message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    deferMessage(message);
+                    break;
+                case CONNECT_TIMEOUT: {
+                    Log.w(TAG, "Disconnecting connection timeout: " + mDevice);
+                    mA2dpNativeInterface.disconnectA2dp(mDevice);
+                    A2dpStackEvent event =
+                            new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+                    event.device = mDevice;
+                    event.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
+                    sendMessage(STACK_EVENT, event);
+                    break;
+                }
+                case DISCONNECT:
+                    deferMessage(message);
+                    break;
+                case STACK_EVENT:
+                    A2dpStackEvent event = (A2dpStackEvent) message.obj;
+                    log("Disconnecting: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt);
+                            break;
+                        case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
+                            processCodecConfigEvent(event.codecStatus);
+                            break;
+                        case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
+                        default:
+                            Log.e(TAG, "Disconnecting: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Disconnecting state
+        private void processConnectionEvent(int event) {
+            switch (event) {
+                case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.i(TAG, "Disconnected: " + mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
+                    if (mA2dpService.okToConnect(mDevice, false)) {
+                        Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice);
+                        transitionTo(mConnected);
+                    } else {
+                        // Reject the connection and stay in Disconnecting state
+                        Log.w(TAG, "Incoming A2DP Connected request rejected: " + mDevice);
+                        mA2dpNativeInterface.disconnectA2dp(mDevice);
+                    }
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
+                    if (mA2dpService.okToConnect(mDevice, false)) {
+                        Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice);
+                        transitionTo(mConnecting);
+                    } else {
+                        // Reject the connection and stay in Disconnecting state
+                        Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice);
+                        mA2dpNativeInterface.disconnectA2dp(mDevice);
+                    }
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    // We are already disconnecting, do nothing
+                    break;
+                default:
+                    Log.e(TAG, "Incorrect event: " + event);
+                    break;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    class Connected extends State {
+        @Override
+        public void enter() {
+            Message currentMessage = getCurrentMessage();
+            Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null"
+                    : messageWhatToString(currentMessage.what)));
+            mConnectionState = BluetoothProfile.STATE_CONNECTED;
+
             removeDeferredMessages(CONNECT);
 
-            log("Enter Connected: " + getCurrentMessage().what);
-            if (mTargetDevice != null || mIncomingDevice != null) {
-                loge("ERROR: enter() inconsistent state in Connected: current = " + mCurrentDevice
-                        + " target = " + mTargetDevice + " incoming = " + mIncomingDevice);
-            }
-
+            broadcastConnectionState(mConnectionState, mLastConnectionState);
             // Upon connected, the audio starts out as stopped
-            broadcastAudioState(mCurrentDevice, BluetoothA2dp.STATE_NOT_PLAYING,
+            broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
                                 BluetoothA2dp.STATE_PLAYING);
         }
 
         @Override
+        public void exit() {
+            Message currentMessage = getCurrentMessage();
+            log("Exit Connected(" + mDevice + "): " + (currentMessage == null ? "null"
+                    : messageWhatToString(currentMessage.what)));
+            mLastConnectionState = BluetoothProfile.STATE_CONNECTED;
+        }
+
+        @Override
         public boolean processMessage(Message message) {
-            log("Connected process message: " + message.what);
-            if (mCurrentDevice == null) {
-                loge("ERROR: mCurrentDevice is null in Connected");
-                return NOT_HANDLED;
-            }
+            log("Connected process message(" + mDevice + "): " + messageWhatToString(message.what));
 
-            boolean retValue = HANDLED;
-            switch(message.what) {
+            switch (message.what) {
                 case CONNECT:
-                {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (mCurrentDevice.equals(device)) {
-                        break;
-                    }
-
-                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                   BluetoothProfile.STATE_DISCONNECTED);
-                    if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                       BluetoothProfile.STATE_CONNECTING);
-                        break;
-                    } else {
-                        broadcastConnectionState(mCurrentDevice,
-                                BluetoothProfile.STATE_DISCONNECTING,
-                                BluetoothProfile.STATE_CONNECTED);
-                    }
-
-                    synchronized (A2dpStateMachine.this) {
-                        mTargetDevice = device;
-                        transitionTo(mPending);
-                    }
-                }
+                    Log.w(TAG, "Connected: CONNECT ignored: " + mDevice);
                     break;
-                case DISCONNECT:
-                {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (!mCurrentDevice.equals(device)) {
+                case DISCONNECT: {
+                    Log.i(TAG, "Disconnecting from " + mDevice);
+                    if (!mA2dpNativeInterface.disconnectA2dp(mDevice)) {
+                        // If error in the native stack, transition directly to Disconnected state.
+                        Log.e(TAG, "Connected: error disconnecting from " + mDevice);
+                        transitionTo(mDisconnected);
                         break;
                     }
-                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
-                                   BluetoothProfile.STATE_CONNECTED);
-                    if (!disconnectA2dpNative(getByteAddress(device))) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTING);
-                        break;
-                    }
-                    synchronized (A2dpStateMachine.this) {
-                        transitionTo(mPending);
-                    }
+                    transitionTo(mDisconnecting);
                 }
-                    break;
+                break;
                 case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
+                    A2dpStackEvent event = (A2dpStackEvent) message.obj;
+                    log("Connected: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
                     switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.valueInt, event.device);
+                        case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt);
                             break;
-                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
-                            processAudioStateEvent(event.valueInt, event.device);
+                        case A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
+                            processAudioStateEvent(event.valueInt);
+                            break;
+                        case A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED:
+                            processCodecConfigEvent(event.codecStatus);
                             break;
                         default:
-                            loge("Unexpected stack event: " + event.type);
+                            Log.e(TAG, "Connected: ignoring stack event: " + event);
                             break;
                     }
                     break;
                 default:
                     return NOT_HANDLED;
             }
-            return retValue;
+            return HANDLED;
         }
 
         // in Connected state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
-            switch (state) {
-                case CONNECTION_STATE_DISCONNECTED:
-                    if (mCurrentDevice.equals(device)) {
-                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                                 BluetoothProfile.STATE_CONNECTED);
-                        synchronized (A2dpStateMachine.this) {
-                            mCurrentDevice = null;
-                            transitionTo(mDisconnected);
-                        }
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                                 BluetoothProfile.STATE_CONNECTING);
-                        synchronized (A2dpStateMachine.this) {
-                            mTargetDevice = null;
-                        }
-                        logi("Disconnected from mTargetDevice in connected state device: " + device);
-                    } else {
-                        loge("Disconnected from unknown device: " + device);
-                    }
+        private void processConnectionEvent(int event) {
+            switch (event) {
+                case A2dpStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.i(TAG, "Disconnected from " + mDevice);
+                    transitionTo(mDisconnected);
                     break;
-              default:
-                  loge("Connection State Device: " + device + " bad state: " + state);
-                  break;
+                case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
+                    Log.w(TAG, "Ignore A2DP CONNECTED event: " + mDevice);
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
+                    Log.w(TAG, "Ignore A2DP CONNECTING event: " + mDevice);
+                    break;
+                case A2dpStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    Log.i(TAG, "Disconnecting from " + mDevice);
+                    transitionTo(mDisconnecting);
+                    break;
+                default:
+                    Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event);
+                    break;
             }
         }
-        private void processAudioStateEvent(int state, BluetoothDevice device) {
-            if (!mCurrentDevice.equals(device)) {
-                loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
-                                                           mCurrentDevice);
-                return;
-            }
+
+        // in Connected state
+        private void processAudioStateEvent(int state) {
             switch (state) {
-                case AUDIO_STATE_STARTED:
-                    if (mPlayingA2dpDevice == null) {
-                        mPlayingA2dpDevice = device;
-                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
-                        broadcastAudioState(device, BluetoothA2dp.STATE_PLAYING,
-                                            BluetoothA2dp.STATE_NOT_PLAYING);
+                case A2dpStackEvent.AUDIO_STATE_STARTED:
+                    synchronized (this) {
+                        if (!mIsPlaying) {
+                            Log.i(TAG, "Connected: started playing: " + mDevice);
+                            mIsPlaying = true;
+                            mA2dpService.setAvrcpAudioState(BluetoothA2dp.STATE_PLAYING);
+                            broadcastAudioState(BluetoothA2dp.STATE_PLAYING,
+                                                BluetoothA2dp.STATE_NOT_PLAYING);
+                        }
                     }
                     break;
-                case AUDIO_STATE_REMOTE_SUSPEND:
-                case AUDIO_STATE_STOPPED:
-                    if (mPlayingA2dpDevice != null) {
-                        mPlayingA2dpDevice = null;
-                        mService.setAvrcpAudioState(BluetoothA2dp.STATE_NOT_PLAYING);
-                        broadcastAudioState(device, BluetoothA2dp.STATE_NOT_PLAYING,
-                                            BluetoothA2dp.STATE_PLAYING);
+                case A2dpStackEvent.AUDIO_STATE_REMOTE_SUSPEND:
+                case A2dpStackEvent.AUDIO_STATE_STOPPED:
+                    synchronized (this) {
+                        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);
+                        }
                     }
                     break;
                 default:
-                  loge("Audio State Device: " + device + " bad state: " + state);
-                  break;
+                    Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state);
+                    break;
             }
         }
     }
 
-    int getConnectionState(BluetoothDevice device) {
-        if (getCurrentState() == mDisconnected) {
-            return BluetoothProfile.STATE_DISCONNECTED;
-        }
+    int getConnectionState() {
+        return mConnectionState;
+    }
 
+    BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    boolean isConnected() {
         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;
-            }
+            return (getCurrentState() == mConnected);
         }
     }
 
-    List<BluetoothDevice> getConnectedDevices() {
-        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+    boolean isPlaying() {
         synchronized (this) {
-            if (getCurrentState() == mConnected) {
-                devices.add(mCurrentDevice);
-            }
+            return mIsPlaying;
         }
-        return devices;
-    }
-
-    boolean isPlaying(BluetoothDevice device) {
-        synchronized (this) {
-            if (device.equals(mPlayingA2dpDevice)) {
-                return true;
-            }
-        }
-        return false;
     }
 
     BluetoothCodecStatus getCodecStatus() {
@@ -790,232 +610,168 @@
         }
     }
 
-    private void onCodecConfigChanged(BluetoothCodecConfig newCodecConfig,
-            BluetoothCodecConfig[] codecsLocalCapabilities,
-            BluetoothCodecConfig[] codecsSelectableCapabilities) {
+    // NOTE: This event is processed in any state
+    private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
         BluetoothCodecConfig prevCodecConfig = null;
         synchronized (this) {
             if (mCodecStatus != null) {
                 prevCodecConfig = mCodecStatus.getCodecConfig();
             }
-            mCodecStatus = new BluetoothCodecStatus(
-                    newCodecConfig, codecsLocalCapabilities, codecsSelectableCapabilities);
+            mCodecStatus = newCodecStatus;
         }
 
-        Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
-        intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, mCodecStatus);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-
-        log("A2DP Codec Config: " + prevCodecConfig + "->" + newCodecConfig);
-        for (BluetoothCodecConfig codecConfig : codecsLocalCapabilities) {
-            log("A2DP Codec Local Capability: " + codecConfig);
-        }
-        for (BluetoothCodecConfig codecConfig : codecsSelectableCapabilities) {
-            log("A2DP Codec Selectable Capability: " + codecConfig);
+        if (DBG) {
+            Log.d(TAG, "A2DP Codec Config: " + prevCodecConfig + "->"
+                    + newCodecStatus.getCodecConfig());
+            for (BluetoothCodecConfig codecConfig :
+                     newCodecStatus.getCodecsLocalCapabilities()) {
+                Log.d(TAG, "A2DP Codec Local Capability: " + codecConfig);
+            }
+            for (BluetoothCodecConfig codecConfig :
+                     newCodecStatus.getCodecsSelectableCapabilities()) {
+                Log.d(TAG, "A2DP Codec Selectable Capability: " + codecConfig);
+            }
         }
 
-        // Inform the Audio Service about the codec configuration change,
-        // so the Audio Service can reset accordingly the audio feeding
-        // parameters in the Audio HAL to the Bluetooth stack.
-        if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig) && (mCurrentDevice != null)
-                && (getCurrentState() == mConnected)) {
-            // Add the device only if it is currently connected
-            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mCurrentDevice);
-            mAudioManager.handleBluetoothA2dpDeviceConfigChange(mCurrentDevice);
-        }
-        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
-    }
-
-    void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
-        BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
-        codecConfigArray[0] = codecConfig;
-        setCodecConfigPreferenceNative(codecConfigArray);
-    }
-
-    void enableOptionalCodecs() {
-        BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
-        if (codecConfigArray == null) {
+        if (mA2dpOffloadEnabled) {
+            boolean update = false;
+            BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
+            if ((prevCodecConfig != null)
+                    && (prevCodecConfig.getCodecType() != newCodecConfig.getCodecType())) {
+                update = true;
+            } else if (!newCodecConfig.sameAudioFeedingParameters(prevCodecConfig)) {
+                update = true;
+            } else if ((newCodecConfig.getCodecType()
+                        == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC)
+                    && (prevCodecConfig != null)
+                    && (prevCodecConfig.getCodecSpecific1()
+                        != newCodecConfig.getCodecSpecific1())) {
+                update = true;
+            }
+            if (update) {
+                mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, false);
+            }
             return;
         }
 
-        // Set the mandatory codec's priority to default, and remove the rest
-        for (int i = 0; i < codecConfigArray.length; i++) {
-            BluetoothCodecConfig codecConfig = codecConfigArray[i];
-            if (!codecConfig.isMandatoryCodec()) {
-                codecConfigArray[i] = null;
-            }
-        }
-
-        setCodecConfigPreferenceNative(codecConfigArray);
+        boolean sameAudioFeedingParameters =
+                newCodecStatus.getCodecConfig().sameAudioFeedingParameters(prevCodecConfig);
+        mA2dpService.codecConfigUpdated(mDevice, mCodecStatus, sameAudioFeedingParameters);
     }
 
-    void disableOptionalCodecs() {
-        BluetoothCodecConfig[] codecConfigArray = assignCodecConfigPriorities();
-        if (codecConfigArray == null) {
-            return;
-        }
-        // Set the mandatory codec's priority to highest, and ignore the rest
-        for (int i = 0; i < codecConfigArray.length; i++) {
-            BluetoothCodecConfig codecConfig = codecConfigArray[i];
-            if (codecConfig.isMandatoryCodec()) {
-                codecConfig.setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST);
-            } else {
-                codecConfigArray[i] = null;
-            }
-        }
-        setCodecConfigPreferenceNative(codecConfigArray);
-    }
-
-    boolean okToConnect(BluetoothDevice device) {
-        AdapterService adapterService = AdapterService.getAdapterService();
-        int priority = mService.getPriority(device);
-        boolean ret = false;
-        //check if this is an incoming connection in Quiet mode.
-        if((adapterService == null) ||
-           ((adapterService.isQuietModeEnabled() == true) &&
-           (mTargetDevice == null))){
-            ret = false;
-        }
-        // check priority and accept or reject the connection. if priority is undefined
-        // it is likely that our SDP has not completed and peer is initiating the
-        // connection. Allow this connection, provided the device is bonded
-        else if((BluetoothProfile.PRIORITY_OFF < priority) ||
-                ((BluetoothProfile.PRIORITY_UNDEFINED == priority) &&
-                (device.getBondState() != BluetoothDevice.BOND_NONE))){
-            if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
-                ret = true;
-            }
-        }
-        return ret;
-    }
-
-    synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
-        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
-        int connectionState;
-
-        for (BluetoothDevice device : bondedDevices) {
-            ParcelUuid[] featureUuids = device.getUuids();
-            if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.AudioSink)) {
-                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) {
-        mAudioManager.setBluetoothA2dpDeviceConnectionState(
-                device, newState, BluetoothProfile.A2DP);
+    private void broadcastConnectionState(int newState, int prevState) {
+        log("Connection state " + mDevice + ": " + profileStateToString(prevState)
+                    + "->" + profileStateToString(newState));
 
-        mWakeLock.acquire();
-        mIntentBroadcastHandler.sendMessage(mIntentBroadcastHandler.obtainMessage(
-                MSG_CONNECTION_STATE_CHANGED, prevState, newState, device));
-    }
-
-    private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
-        Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mA2dpService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
+
+    private void broadcastAudioState(int newState, int prevState) {
+        log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState)
+                + "->" + audioStateToString(newState));
+
+        Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
-
-        log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
+        mA2dpService.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
     }
 
-    private byte[] getByteAddress(BluetoothDevice device) {
-        return Utils.getBytesFromAddress(device.getAddress());
+    @Override
+    protected String getLogRecString(Message msg) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(messageWhatToString(msg.what));
+        builder.append(": ");
+        builder.append("arg1=")
+                .append(msg.arg1)
+                .append(", arg2=")
+                .append(msg.arg2)
+                .append(", obj=")
+                .append(msg.obj);
+        return builder.toString();
     }
 
-    private void onConnectionStateChanged(int state, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
-        event.valueInt = state;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAudioStateChanged(int state, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
-        event.valueInt = state;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-    private BluetoothDevice getDevice(byte[] address) {
-        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
-    }
-
-    private class StackEvent {
-        int type = EVENT_TYPE_NONE;
-        int valueInt = 0;
-        BluetoothDevice device = null;
-
-        private StackEvent(int type) {
-            this.type = type;
+    private static String messageWhatToString(int what) {
+        switch (what) {
+            case CONNECT:
+                return "CONNECT";
+            case DISCONNECT:
+                return "DISCONNECT";
+            case STACK_EVENT:
+                return "STACK_EVENT";
+            case CONNECT_TIMEOUT:
+                return "CONNECT_TIMEOUT";
+            default:
+                break;
         }
+        return Integer.toString(what);
     }
-    /** Handles A2DP connection state change intent broadcasts. */
-    private class IntentBroadcastHandler extends Handler {
 
-        private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
-            Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
-            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
-            intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
-            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
-            log("Connection state " + device + ": " + prevState + "->" + state);
+    private static String profileStateToString(int state) {
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return "DISCONNECTED";
+            case BluetoothProfile.STATE_CONNECTING:
+                return "CONNECTING";
+            case BluetoothProfile.STATE_CONNECTED:
+                return "CONNECTED";
+            case BluetoothProfile.STATE_DISCONNECTING:
+                return "DISCONNECTING";
+            default:
+                break;
         }
+        return Integer.toString(state);
+    }
 
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_CONNECTION_STATE_CHANGED:
-                    onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
-                    mWakeLock.release();
-                    break;
-            }
+    private static String audioStateToString(int state) {
+        switch (state) {
+            case BluetoothA2dp.STATE_PLAYING:
+                return "PLAYING";
+            case BluetoothA2dp.STATE_NOT_PLAYING:
+                return "NOT_PLAYING";
+            default:
+                break;
         }
+        return Integer.toString(state);
     }
 
     public void dump(StringBuilder sb) {
-        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
-        ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
-        ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
-        ProfileService.println(sb, "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
-        ProfileService.println(sb, "StateMachine: " + this.toString());
+        ProfileService.println(sb, "mDevice: " + mDevice);
+        ProfileService.println(sb, "  StateMachine: " + this.toString());
+        ProfileService.println(sb, "  mIsPlaying: " + mIsPlaying);
+        synchronized (this) {
+            if (mCodecStatus != null) {
+                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);
+        super.dump(new FileDescriptor(), printWriter, new String[]{});
+        printWriter.flush();
+        stringWriter.flush();
+        ProfileService.println(sb, "  StateMachineLog:");
+        Scanner scanner = new Scanner(stringWriter.toString());
+        while (scanner.hasNextLine()) {
+            String line = scanner.nextLine();
+            ProfileService.println(sb, "    " + line);
+        }
+        scanner.close();
     }
 
-    // Event types for STACK_EVENT message
-    final private static int EVENT_TYPE_NONE = 0;
-    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
-    final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
-
-   // Do not modify without updating the HAL bt_av.h files.
-
-    // match up with btav_connection_state_t enum of bt_av.h
-    final static int CONNECTION_STATE_DISCONNECTED = 0;
-    final static int CONNECTION_STATE_CONNECTING = 1;
-    final static int CONNECTION_STATE_CONNECTED = 2;
-    final static int CONNECTION_STATE_DISCONNECTING = 3;
-
-    // match up with btav_audio_state_t enum of bt_av.h
-    final static int AUDIO_STATE_REMOTE_SUSPEND = 0;
-    final static int AUDIO_STATE_STOPPED = 1;
-    final static int AUDIO_STATE_STARTED = 2;
-
-    private native static void classInitNative();
-    private native void initNative(BluetoothCodecConfig[] codecConfigPriorites);
-    private native void cleanupNative();
-    private native boolean connectA2dpNative(byte[] address);
-    private native boolean disconnectA2dpNative(byte[] address);
-    private native boolean setCodecConfigPreferenceNative(BluetoothCodecConfig[] codecConfigArray);
+    @Override
+    protected void log(String msg) {
+        if (DBG) {
+            super.log(msg);
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index a7426e9..17c8885 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -24,11 +24,10 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
-import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
-
-import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.btservice.ProfileService;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -44,14 +43,12 @@
     private A2dpSinkStateMachine mStateMachine;
     private static A2dpSinkService sA2dpSinkService;
 
-    protected String getName() {
-        return TAG;
-    }
-
+    @Override
     protected IProfileServiceBinder initBinder() {
         return new BluetoothA2dpSinkBinder(this);
     }
 
+    @Override
     protected boolean start() {
         if (DBG) {
             Log.d(TAG, "start()");
@@ -64,11 +61,13 @@
         return true;
     }
 
+    @Override
     protected boolean stop() {
         if (DBG) {
             Log.d(TAG, "stop()");
         }
-        if(mStateMachine != null) {
+        setA2dpSinkService(null);
+        if (mStateMachine != null) {
             mStateMachine.doQuit();
         }
         Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class);
@@ -76,57 +75,40 @@
         return true;
     }
 
-    protected boolean cleanup() {
-        if (mStateMachine!= null) {
+    @Override
+    protected void cleanup() {
+        if (mStateMachine != null) {
             mStateMachine.cleanup();
         }
-        clearA2dpSinkService();
-        return true;
     }
 
     //API Methods
 
-    public static synchronized A2dpSinkService getA2dpSinkService(){
-        if (sA2dpSinkService != null && sA2dpSinkService.isAvailable()) {
-            if (DBG) Log.d(TAG, "getA2dpSinkService(): returning " + sA2dpSinkService);
-            return sA2dpSinkService;
+    public static synchronized A2dpSinkService getA2dpSinkService() {
+        if (sA2dpSinkService == null) {
+            Log.w(TAG, "getA2dpSinkService(): service is null");
+            return null;
         }
-        if (DBG)  {
-            if (sA2dpSinkService == null) {
-                Log.d(TAG, "getA2dpSinkService(): service is NULL");
-            } else if (!(sA2dpSinkService.isAvailable())) {
-                Log.d(TAG,"getA2dpSinkService(): service is not available");
-            }
+        if (!sA2dpSinkService.isAvailable()) {
+            Log.w(TAG, "getA2dpSinkService(): service is not available ");
+            return null;
         }
-        return null;
+        return sA2dpSinkService;
     }
 
     private static synchronized void setA2dpSinkService(A2dpSinkService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) Log.d(TAG, "setA2dpSinkService(): set to: " + sA2dpSinkService);
-            sA2dpSinkService = instance;
-        } else {
-            if (DBG)  {
-                if (sA2dpSinkService == null) {
-                    Log.d(TAG, "setA2dpSinkService(): service not available");
-                } else if (!sA2dpSinkService.isAvailable()) {
-                    Log.d(TAG,"setA2dpSinkService(): service is cleaning up");
-                }
-            }
+        if (DBG) {
+            Log.d(TAG, "setA2dpSinkService(): set to: " + instance);
         }
-    }
-
-    private static synchronized void clearA2dpSinkService() {
-        sA2dpSinkService = null;
+        sA2dpSinkService = instance;
     }
 
     public boolean connect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
 
         int connectionState = mStateMachine.getConnectionState(device);
-        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
-            connectionState == BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState == BluetoothProfile.STATE_CONNECTED
+                || connectionState == BluetoothProfile.STATE_CONNECTING) {
             return false;
         }
 
@@ -139,11 +121,10 @@
     }
 
     boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         int connectionState = mStateMachine.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
-            connectionState != BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
             return false;
         }
 
@@ -167,23 +148,20 @@
     }
 
     public boolean setPriority(BluetoothDevice device, int priority) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH_ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Settings.Global.putInt(getContentResolver(),
-            Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
-            priority);
+                Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()), priority);
         if (DBG) {
-            Log.d(TAG,"Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH_ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         int priority = Settings.Global.getInt(getContentResolver(),
-            Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
-            BluetoothProfile.PRIORITY_UNDEFINED);
+                Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
+                BluetoothProfile.PRIORITY_UNDEFINED);
         return priority;
     }
 
@@ -197,12 +175,12 @@
      */
     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) {
+            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) {
+            } 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);
             }
         }
@@ -225,9 +203,21 @@
         }
     }
 
+    /**
+     * 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");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (DBG) {
             Log.d(TAG, "isA2dpPlaying(" + device + ")");
         }
@@ -241,12 +231,12 @@
 
     //Binder object: Must be static class or memory leak may occur
     private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub
-        implements IProfileServiceBinder {
+            implements IProfileServiceBinder {
         private A2dpSinkService mService;
 
         private A2dpSinkService getService() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"A2dp call not allowed for non-active user");
+                Log.w(TAG, "A2dp call not allowed for non-active user");
                 return null;
             }
 
@@ -260,65 +250,94 @@
             mService = svc;
         }
 
-        public boolean cleanup()  {
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
+        @Override
         public boolean connect(BluetoothDevice device) {
             A2dpSinkService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.connect(device);
         }
 
+        @Override
         public boolean disconnect(BluetoothDevice device) {
             A2dpSinkService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.disconnect(device);
         }
 
+        @Override
         public List<BluetoothDevice> getConnectedDevices() {
             A2dpSinkService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getConnectedDevices();
         }
 
+        @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
             A2dpSinkService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getDevicesMatchingConnectionStates(states);
         }
 
+        @Override
         public int getConnectionState(BluetoothDevice device) {
             A2dpSinkService service = getService();
-            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
             return service.getConnectionState(device);
         }
 
+        @Override
         public boolean isA2dpPlaying(BluetoothDevice device) {
             A2dpSinkService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isA2dpPlaying(device);
         }
 
+        @Override
         public boolean setPriority(BluetoothDevice device, int priority) {
             A2dpSinkService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPriority(device, priority);
         }
 
+        @Override
         public int getPriority(BluetoothDevice device) {
             A2dpSinkService service = getService();
-            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
+            if (service == null) {
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
             return service.getPriority(device);
         }
 
+        @Override
         public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
             A2dpSinkService service = getService();
-            if (service == null) return null;
+            if (service == null) {
+                return null;
+            }
             return service.getAudioConfig(device);
         }
-    };
+    }
+
+    ;
 
     @Override
     public void dump(StringBuilder sb) {
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index 214db99..fb64318 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -34,28 +34,29 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
-import android.content.BroadcastReceiver;
 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.os.PowerManager.WakeLock;
 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.ProfileService;
 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.List;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Set;
 
 public class A2dpSinkStateMachine extends StateMachine {
@@ -69,6 +70,7 @@
     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;
 
     private static final int IS_INVALID_DEVICE = 0;
     private static final int IS_VALID_DEVICE = 1;
@@ -122,8 +124,8 @@
     private BluetoothDevice mPlayingDevice = null;
     private A2dpSinkStreamHandler mStreaming = null;
 
-    private final HashMap<BluetoothDevice,BluetoothAudioConfig> mAudioConfigs
-            = new HashMap<BluetoothDevice,BluetoothAudioConfig>();
+    private final HashMap<BluetoothDevice, BluetoothAudioConfig> mAudioConfigs =
+            new HashMap<BluetoothDevice, BluetoothAudioConfig>();
 
     static {
         classInitNative();
@@ -147,7 +149,7 @@
 
         setInitialState(mDisconnected);
 
-        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
 
         mIntentBroadcastHandler = new IntentBroadcastHandler();
     }
@@ -160,11 +162,11 @@
     }
 
     public void doQuit() {
-        if(DBG) {
+        if (DBG) {
             Log.d("A2dpSinkStateMachine", "Quit");
         }
         synchronized (A2dpSinkStateMachine.this) {
-                mStreaming = null;
+            mStreaming = null;
         }
         quitNow();
     }
@@ -190,21 +192,21 @@
         @Override
         public boolean processMessage(Message message) {
             log("Disconnected process message: " + message.what);
-            if (mCurrentDevice != null || mTargetDevice != null  || mIncomingDevice != null) {
+            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) {
+            switch (message.what) {
                 case CONNECT:
                     BluetoothDevice device = (BluetoothDevice) message.obj;
                     broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                   BluetoothProfile.STATE_DISCONNECTED);
+                            BluetoothProfile.STATE_DISCONNECTED);
 
-                    if (!connectA2dpNative(getByteAddress(device)) ) {
+                    if (!connectA2dpNative(getByteAddress(device))) {
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                       BluetoothProfile.STATE_CONNECTING);
+                                BluetoothProfile.STATE_CONNECTING);
                         break;
                     }
 
@@ -223,10 +225,10 @@
                     StackEvent event = (StackEvent) message.obj;
                     switch (event.type) {
                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.valueInt, event.device);
+                            processConnectionEvent(event.device, event.valueInt);
                             break;
                         case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
-                            processAudioConfigEvent(event.audioConfig, event.device);
+                            processAudioConfigEvent(event.device, event.audioConfig);
                             break;
                         default:
                             loge("Unexpected stack event: " + event.type);
@@ -245,48 +247,48 @@
         }
 
         // in Disconnected state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
+        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);
+                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));
                     }
-                } 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);
+                    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));
                     }
-                } 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;
+                    break;
+                case CONNECTION_STATE_DISCONNECTING:
+                    logw("Ignore HF DISCONNECTING event, device: " + device);
+                    break;
+                default:
+                    loge("Incorrect state: " + state);
+                    break;
             }
         }
     }
@@ -302,26 +304,24 @@
             log("Pending process message: " + message.what);
 
             boolean retValue = HANDLED;
-            switch(message.what) {
+            switch (message.what) {
                 case CONNECT:
-                    deferMessage(message);
+                    logd("Disconnect before connecting to another target");
                     break;
                 case CONNECT_TIMEOUT:
-                    onConnectionStateChanged(CONNECTION_STATE_DISCONNECTED,
-                                             getByteAddress(mTargetDevice));
+                    onConnectionStateChanged(getByteAddress(mTargetDevice),
+                                             CONNECTION_STATE_DISCONNECTED);
                     break;
                 case DISCONNECT:
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (mCurrentDevice != null && mTargetDevice != null &&
-                        mTargetDevice.equals(device) ) {
+                    if (mCurrentDevice != null && mTargetDevice != null && mTargetDevice.equals(
+                            device)) {
                         // cancel connection to the mTargetDevice
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                       BluetoothProfile.STATE_CONNECTING);
+                                BluetoothProfile.STATE_CONNECTING);
                         synchronized (A2dpSinkStateMachine.this) {
                             mTargetDevice = null;
                         }
-                    } else {
-                        deferMessage(message);
                     }
                     break;
                 case STACK_EVENT:
@@ -330,10 +330,10 @@
                     switch (event.type) {
                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
                             removeMessages(CONNECT_TIMEOUT);
-                            processConnectionEvent(event.valueInt, event.device);
+                            processConnectionEvent(event.device, event.valueInt);
                             break;
                         case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
-                            processAudioConfigEvent(event.audioConfig, event.device);
+                            processAudioConfigEvent(event.device, event.audioConfig);
                             break;
                         default:
                             loge("Unexpected stack event: " + event.type);
@@ -347,17 +347,17 @@
         }
 
         // in Pending state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
+        private void processConnectionEvent(BluetoothDevice device, int state) {
             log("processConnectionEvent state " + state);
-            log("Devices curr: " + mCurrentDevice + " target: " + mTargetDevice +
-                " incoming: " + mIncomingDevice + " device: " + device);
+            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);
+                                BluetoothProfile.STATE_DISCONNECTED,
+                                BluetoothProfile.STATE_DISCONNECTING);
                         synchronized (A2dpSinkStateMachine.this) {
                             mCurrentDevice = null;
                         }
@@ -365,8 +365,8 @@
                         if (mTargetDevice != null) {
                             if (!connectA2dpNative(getByteAddress(mTargetDevice))) {
                                 broadcastConnectionState(mTargetDevice,
-                                                         BluetoothProfile.STATE_DISCONNECTED,
-                                                         BluetoothProfile.STATE_CONNECTING);
+                                        BluetoothProfile.STATE_DISCONNECTED,
+                                        BluetoothProfile.STATE_CONNECTING);
                                 synchronized (A2dpSinkStateMachine.this) {
                                     mTargetDevice = null;
                                     transitionTo(mDisconnected);
@@ -381,15 +381,15 @@
                     } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
                         // outgoing connection failed
                         broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                                 BluetoothProfile.STATE_CONNECTING);
+                                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);
+                                BluetoothProfile.STATE_DISCONNECTED,
+                                BluetoothProfile.STATE_CONNECTING);
                         synchronized (A2dpSinkStateMachine.this) {
                             mIncomingDevice = null;
                             transitionTo(mDisconnected);
@@ -398,89 +398,90 @@
                         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);
+                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);
+                        }
                     }
-                    synchronized (A2dpSinkStateMachine.this) {
-                        mTargetDevice = 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");
                     }
-                } 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);
+                    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);
                     }
-                } 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;
+                    break;
+                default:
+                    loge("Incorrect state: " + state);
+                    break;
             }
         }
 
@@ -492,15 +493,18 @@
             log("Enter Connected: " + getCurrentMessage().what);
             // Upon connected, the audio starts out as stopped
             broadcastAudioState(mCurrentDevice, BluetoothA2dpSink.STATE_NOT_PLAYING,
-                                BluetoothA2dpSink.STATE_PLAYING);
+                    BluetoothA2dpSink.STATE_PLAYING);
             synchronized (A2dpSinkStateMachine.this) {
                 if (mStreaming == null) {
-                    if(DBG) {
+                    if (DBG) {
                         log("Creating New A2dpSinkStreamHandler");
                     }
                     mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this, mContext);
                 }
             }
+            if (mStreaming.getAudioFocus() == AudioManager.AUDIOFOCUS_NONE) {
+                informAudioFocusStateNative(0);
+            }
         }
 
         @Override
@@ -511,41 +515,21 @@
                 return NOT_HANDLED;
             }
 
-            switch(message.what) {
+            switch (message.what) {
                 case CONNECT:
-                {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (mCurrentDevice.equals(device)) {
-                        break;
-                    }
-
-                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                   BluetoothProfile.STATE_DISCONNECTED);
-                    if (!disconnectA2dpNative(getByteAddress(mCurrentDevice))) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                       BluetoothProfile.STATE_CONNECTING);
-                        break;
-                    }
-
-                    synchronized (A2dpSinkStateMachine.this) {
-                        mTargetDevice = device;
-                        mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget();
-                        transitionTo(mPending);
-                    }
-                }
+                    logd("Disconnect before connecting to another target");
                 break;
 
-                case DISCONNECT:
-                {
+                case DISCONNECT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
                     if (!mCurrentDevice.equals(device)) {
                         break;
                     }
                     broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
-                                   BluetoothProfile.STATE_CONNECTED);
+                            BluetoothProfile.STATE_CONNECTED);
                     if (!disconnectA2dpNative(getByteAddress(device))) {
                         broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                       BluetoothProfile.STATE_DISCONNECTED);
+                                BluetoothProfile.STATE_DISCONNECTED);
                         break;
                     }
                     mPlayingDevice = null;
@@ -558,13 +542,13 @@
                     StackEvent event = (StackEvent) message.obj;
                     switch (event.type) {
                         case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.valueInt, event.device);
+                            processConnectionEvent(event.device, event.valueInt);
                             break;
                         case EVENT_TYPE_AUDIO_STATE_CHANGED:
-                            processAudioStateEvent(event.valueInt, event.device);
+                            processAudioStateEvent(event.device, event.valueInt);
                             break;
                         case EVENT_TYPE_AUDIO_CONFIG_CHANGED:
-                            processAudioConfigEvent(event.audioConfig, event.device);
+                            processAudioConfigEvent(event.device, event.audioConfig);
                             break;
                         default:
                             loge("Unexpected stack event: " + event.type);
@@ -588,6 +572,10 @@
                     mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_PAUSE).sendToTarget();
                     break;
 
+                case EVENT_REQUEST_FOCUS:
+                    mStreaming.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS).sendToTarget();
+                    break;
+
                 default:
                     return NOT_HANDLED;
             }
@@ -595,7 +583,7 @@
         }
 
         // in Connected state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
+        private void processConnectionEvent(BluetoothDevice device, int state) {
             switch (state) {
                 case CONNECTION_STATE_DISCONNECTED:
                     mAudioConfigs.remove(device);
@@ -603,8 +591,9 @@
                         mPlayingDevice = null;
                     }
                     if (mCurrentDevice.equals(device)) {
-                        broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                                 BluetoothProfile.STATE_CONNECTED);
+                        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)
@@ -616,16 +605,16 @@
                         loge("Disconnected from unknown device: " + device);
                     }
                     break;
-              default:
-                  loge("Connection State Device: " + device + " bad state: " + state);
-                  break;
+                default:
+                    loge("Connection State Device: " + device + " bad state: " + state);
+                    break;
             }
         }
 
-        private void processAudioStateEvent(int state, BluetoothDevice device) {
+        private void processAudioStateEvent(BluetoothDevice device, int state) {
             if (!mCurrentDevice.equals(device)) {
-                loge("Audio State Device:" + device + "is different from ConnectedDevice:" +
-                                                           mCurrentDevice);
+                loge("Audio State Device:" + device + "is different from ConnectedDevice:"
+                        + mCurrentDevice);
                 return;
             }
             log(" processAudioStateEvent in state " + state);
@@ -638,13 +627,13 @@
                     mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
                     break;
                 default:
-                  loge("Audio State Device: " + device + " bad state: " + state);
-                  break;
+                    loge("Audio State Device: " + device + " bad state: " + state);
+                    break;
             }
         }
     }
 
-    private void processAudioConfigEvent(BluetoothAudioConfig audioConfig, BluetoothDevice device) {
+    private void processAudioConfigEvent(BluetoothDevice device, BluetoothAudioConfig audioConfig) {
         log("processAudioConfigEvent: " + device);
         mAudioConfigs.put(device, audioConfig);
         broadcastAudioConfig(device, audioConfig);
@@ -688,7 +677,7 @@
 
     List<BluetoothDevice> getConnectedDevices() {
         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-        synchronized(this) {
+        synchronized (this) {
             if (getCurrentState() == mConnected) {
                 devices.add(mCurrentDevice);
             }
@@ -697,7 +686,7 @@
     }
 
     boolean isPlaying(BluetoothDevice device) {
-        synchronized(this) {
+        synchronized (this) {
             if ((mPlayingDevice != null) && (device.equals(mPlayingDevice))) {
                 return true;
             }
@@ -713,12 +702,10 @@
         // 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))){
-            if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
-                return true;
-            }
+        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;
@@ -735,7 +722,7 @@
                 continue;
             }
             connectionState = getConnectionState(device);
-            for(int i = 0; i < states.length; i++) {
+            for (int i = 0; i < states.length; i++) {
                 if (connectionState == states[i]) {
                     deviceList.add(device);
                 }
@@ -748,13 +735,10 @@
     // 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);
+        int delay = 0;
+        mIntentBroadcastHandler.sendMessageDelayed(
+                mIntentBroadcastHandler.obtainMessage(MSG_CONNECTION_STATE_CHANGED, prevState,
+                        newState, device), delay);
     }
 
     private void broadcastAudioState(BluetoothDevice device, int state, int prevState) {
@@ -782,27 +766,27 @@
         return Utils.getBytesFromAddress(device.getAddress());
     }
 
-    private void onConnectionStateChanged(int state, byte[] address) {
+    private void onConnectionStateChanged(byte[] address, int state) {
         StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
-        event.valueInt = state;
         event.device = getDevice(address);
+        event.valueInt = state;
         sendMessage(STACK_EVENT, event);
     }
 
-    private void onAudioStateChanged(int state, byte[] address) {
+    private void onAudioStateChanged(byte[] address, int state) {
         StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
-        event.valueInt = state;
         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);
-        int channelConfig = (channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO
-                                               : AudioFormat.CHANNEL_IN_STEREO);
-        event.audioConfig = new BluetoothAudioConfig(sampleRate, channelConfig,
-                AudioFormat.ENCODING_PCM_16BIT);
         event.device = getDevice(address);
+        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);
     }
 
@@ -811,10 +795,10 @@
     }
 
     private class StackEvent {
-        int type = EVENT_TYPE_NONE;
-        int valueInt = 0;
-        BluetoothDevice device = null;
-        BluetoothAudioConfig audioConfig = null;
+        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;
@@ -825,6 +809,9 @@
     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);
@@ -844,49 +831,56 @@
         }
     }
 
-    public boolean SendPassThruPlay(BluetoothDevice mDevice) {
-            log("SendPassThruPlay + ");
-            AvrcpControllerService avrcpCtrlService = AvrcpControllerService.getAvrcpControllerService();
-            if ((avrcpCtrlService != null) && (mDevice != null) &&
-                (avrcpCtrlService.getConnectedDevices().contains(mDevice))){
-                avrcpCtrlService.sendPassThroughCmd(mDevice,
+    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,
+            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;
-            }
+            log(" sendPassThruPlay command sent - ");
+            return true;
+        } else {
+            log("passthru command not sent, connection unavailable");
+            return false;
         }
+    }
 
     // Event types for STACK_EVENT message
-    final private static int EVENT_TYPE_NONE = 0;
-    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
-    final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
-    final private static int EVENT_TYPE_AUDIO_CONFIG_CHANGED = 3;
+    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;
 
     // Do not modify without updating the HAL bt_av.h files.
 
     // match up with btav_connection_state_t enum of bt_av.h
-    final static int CONNECTION_STATE_DISCONNECTED = 0;
-    final static int CONNECTION_STATE_CONNECTING = 1;
-    final static int CONNECTION_STATE_CONNECTED = 2;
-    final static int CONNECTION_STATE_DISCONNECTING = 3;
+    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
-    final static int AUDIO_STATE_REMOTE_SUSPEND = 0;
-    final static int AUDIO_STATE_STOPPED = 1;
-    final static int AUDIO_STATE_STARTED = 2;
+    static final int AUDIO_STATE_REMOTE_SUSPEND = 0;
+    static final int AUDIO_STATE_STOPPED = 1;
+    static final int AUDIO_STATE_STARTED = 2;
 
-    private native static void classInitNative();
+    private static native void classInitNative();
+
     private native void initNative();
+
     private native void cleanupNative();
+
     private native boolean connectA2dpNative(byte[] address);
+
     private native boolean disconnectA2dpNative(byte[] address);
+
     public native void informAudioFocusStateNative(int focusGranted);
+
     public native void informAudioTrackGainNative(float focusGranted);
 }
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index dae6df5..f442c49 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioFocusRequest;
 import android.media.AudioManager;
@@ -26,8 +27,8 @@
 import android.os.Message;
 import android.util.Log;
 
-import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
 import com.android.bluetooth.R;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
 
 import java.util.List;
 
@@ -55,6 +56,7 @@
 
     // Configuration Variables
     private static final int DEFAULT_DUCK_PERCENT = 25;
+    private static final int SETTLE_TIMEOUT = 1000;
 
     // Incoming events.
     public static final int SRC_STR_START = 0; // Audio stream from remote device started
@@ -65,6 +67,8 @@
     public static final int SRC_PAUSE = 5; // Pause command was generated from remote device
     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
 
     // Used to indicate focus lost
     private static final int STATE_FOCUS_LOST = 0;
@@ -83,6 +87,7 @@
 
     // Focus changes when we are currently holding focus.
     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
+        @Override
         public void onAudioFocusChange(int focusChange) {
             if (DBG) {
                 Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange);
@@ -106,8 +111,14 @@
         }
         switch (message.what) {
             case SRC_STR_START:
-                // Audio stream has started, stop it if we don't have focus.
                 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 {
@@ -135,7 +146,16 @@
                 break;
 
             case SRC_PLAY:
-                // Remote play command, if we have audio focus update avrcp, otherwise send pause.
+                // Remote play command.
+                // If is an iot device gain focus and start avrcp updates.
+                if (isIotDevice() || isTvDevice()) {
+                    if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
+                        requestAudioFocus();
+                    }
+                    startAvrcpUpdates();
+                    break;
+                }
+                // Otherwise, pause if we don't have focus
                 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
                     sendAvrcpPause();
                 } else {
@@ -148,11 +168,15 @@
                 stopAvrcpUpdates();
                 break;
 
+            case REQUEST_FOCUS:
+                if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
+                    requestAudioFocus();
+                }
+                break;
+
             case DISCONNECT:
                 // Remote device has disconnected, restore everything to default state.
-                sendAvrcpPause();
                 stopAvrcpUpdates();
-                abandonAudioFocus();
                 mSentPause = false;
                 break;
 
@@ -164,15 +188,14 @@
                         startAvrcpUpdates();
                         startFluorideStreaming();
                         if (mSentPause) {
-                            sendAvrcpPlay();
-                            mSentPause = false;
+                            sendMessageDelayed(obtainMessage(DELAYED_RESUME), SETTLE_TIMEOUT);
                         }
                         break;
 
                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                         // Make the volume duck.
-                        int duckPercent = mContext.getResources().getInteger(
-                                R.integer.a2dp_sink_duck_percent);
+                        int duckPercent = mContext.getResources()
+                                .getInteger(R.integer.a2dp_sink_duck_percent);
                         if (duckPercent < 0 || duckPercent > 100) {
                             Log.e(TAG, "Invalid duck percent using default.");
                             duckPercent = DEFAULT_DUCK_PERCENT;
@@ -197,13 +220,19 @@
                     case AudioManager.AUDIOFOCUS_LOSS:
                         // Permanent loss of focus probably due to another audio app, abandon focus
                         // and stop playback.
-                        mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
                         abandonAudioFocus();
                         sendAvrcpPause();
                         break;
                 }
                 break;
 
+            case DELAYED_RESUME:
+                // Resume playback after source and sink states settle.
+                sendAvrcpPlay();
+                mSentPause = false;
+                break;
+
+
             default:
                 Log.w(TAG, "Received unexpected event: " + message.what);
         }
@@ -212,19 +241,18 @@
     /**
      * Utility functions.
      */
-    private int requestAudioFocus() {
+    private synchronized int 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)
+                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().
         AudioFocusRequest focusRequest =
-                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
-                        .setAudioAttributes(streamAttributes)
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(
+                        streamAttributes)
                         .setWillPauseWhenDucked(true)
                         .setOnAudioFocusChangeListener(mAudioFocusListener, this)
                         .build();
@@ -239,7 +267,7 @@
     }
 
 
-    private void abandonAudioFocus() {
+    private synchronized void abandonAudioFocus() {
         stopFluorideStreaming();
         mAudioManager.abandonAudioFocus(mAudioFocusListener);
         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
@@ -337,4 +365,17 @@
             Log.e(TAG, "Passthrough not sent, connection un-available.");
         }
     }
+
+    synchronized int getAudioFocus() {
+        return mAudioFocus;
+    }
+
+    private boolean isIotDevice() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED);
+    }
+
+    private boolean isTvDevice() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
 }
diff --git a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
index 84dec4f..739f17c 100644
--- a/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
+++ b/src/com/android/bluetooth/a2dpsink/mbs/A2dpMediaBrowserService.java
@@ -16,7 +16,6 @@
 
 package com.android.bluetooth.a2dpsink.mbs;
 
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAvrcpController;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -24,10 +23,9 @@
 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.MediaDescription;
-import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
@@ -36,17 +34,18 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcelable;
-import android.os.ResultReceiver;
 import android.service.media.MediaBrowserService;
-import android.util.Pair;
 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;
@@ -67,6 +66,9 @@
  */
 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;
 
@@ -88,10 +90,12 @@
     private static final int MSG_FOLDER_LIST = 9;
 
     // Custom actions for PTS testing.
-    private String CUSTOM_ACTION_VOL_UP = "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_UP";
-    private String CUSTOM_ACTION_VOL_DN = "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_VOL_DN";
-    private String CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE =
-        "com.android.bluetooth.a2dpsink.mbs.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
+    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;
@@ -99,9 +103,9 @@
     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<>();
-    private static final List<MediaItem> mEmptyList = new ArrayList<MediaItem>();
 
     // Browsing related structures.
     private List<MediaItem> mNowPlayingList = null;
@@ -134,7 +138,7 @@
                     break;
                 case MSG_TRACK:
                     Pair<PlaybackState, MediaMetadata> pair =
-                        (Pair<PlaybackState, MediaMetadata>) (msg.obj);
+                            (Pair<PlaybackState, MediaMetadata>) (msg.obj);
                     inst.msgTrack(pair.first, pair.second);
                     break;
                 case MSG_AVRCP_PASSTHRU:
@@ -160,14 +164,14 @@
 
     @Override
     public void onCreate() {
-        Log.d(TAG, "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.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
         mSession.setActive(true);
         mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
 
@@ -187,7 +191,7 @@
 
     @Override
     public void onDestroy() {
-        Log.d(TAG, "onDestroy");
+        if (DBG) Log.d(TAG, "onDestroy");
         mSession.release();
         unregisterReceiver(mBtReceiver);
         super.onDestroy();
@@ -199,17 +203,17 @@
     }
 
     @Override
-    public synchronized void onLoadChildren(
-            final String parentMediaId, final Result<List<MediaItem>> result) {
+    public synchronized void onLoadChildren(final String parentMediaId,
+            final Result<List<MediaItem>> result) {
         if (mAvrcpCtrlSrvc == null) {
-            Log.e(TAG, "AVRCP not yet connected.");
-            result.sendResult(mEmptyList);
+            Log.w(TAG, "AVRCP not yet connected.");
+            result.sendResult(Collections.emptyList());
             return;
         }
 
-        Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
+        if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
         if (!mAvrcpCtrlSrvc.getChildren(mA2dpDevice, parentMediaId, 0, 0xff)) {
-            result.sendResult(mEmptyList);
+            result.sendResult(Collections.emptyList());
             return;
         }
 
@@ -229,60 +233,64 @@
     private MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
         @Override
         public void onPlay() {
-            Log.d(TAG, "onPlay");
-            mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY).sendToTarget();
+            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() {
-            Log.d(TAG, "onPause");
-            mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE).sendToTarget();
+            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() {
-            Log.d(TAG, "onSkipToNext");
-            mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD)
-                .sendToTarget();
+            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() {
-            Log.d(TAG, "onSkipToPrevious");
-
-            mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD)
-                .sendToTarget();
+            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() {
-            Log.d(TAG, "onStop");
-            mAvrcpCommandQueue.obtainMessage(
-                    MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP)
-                    .sendToTarget();
+            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() {
-            Log.d(TAG, "onRewind");
-            mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_REWIND).sendToTarget();
+            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() {
-            Log.d(TAG, "onFastForward");
-            mAvrcpCommandQueue.obtainMessage(
-                MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FF).sendToTarget();
+            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.
         }
 
@@ -302,19 +310,16 @@
         // Support VOL UP and VOL DOWN events for PTS testing.
         @Override
         public void onCustomAction(String action, Bundle extras) {
-            Log.d(TAG, "onCustomAction " + action);
+            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();
+                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();
+                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 {
+                mAvrcpCommandQueue.obtainMessage(MSG_AVRCP_GET_PLAY_STATUS_NATIVE).sendToTarget();
+            } else {
                 Log.w(TAG, "Custom action " + action + " not supported.");
             }
         }
@@ -323,15 +328,17 @@
     private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            Log.d(TAG, "onReceive 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)) {
-                Log.d(TAG, "handleConnectionStateChange: newState="
-                        + state + " btDev=" + btDev);
+                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) {
@@ -346,22 +353,21 @@
                     }
                 }
             } else if (AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED.equals(
-                action)) {
+                    action)) {
                 if (state == BluetoothProfile.STATE_CONNECTED) {
-                    mAvrcpCommandQueue.obtainMessage(
-                        MSG_DEVICE_BROWSE_CONNECT, btDev).sendToTarget();
+                    mAvrcpCommandQueue.obtainMessage(MSG_DEVICE_BROWSE_CONNECT, btDev)
+                            .sendToTarget();
                 } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
-                    mAvrcpCommandQueue.obtainMessage(
-                        MSG_DEVICE_BROWSE_DISCONNECT, btDev).sendToTarget();
+                    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();
+                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();
             }
@@ -369,7 +375,7 @@
     };
 
     private synchronized void msgDeviceConnect(BluetoothDevice device) {
-        Log.d(TAG, "msgDeviceConnect");
+        if (DBG) Log.d(TAG, "msgDeviceConnect");
         // We are connected to a new device via A2DP now.
         mA2dpDevice = device;
         mAvrcpCtrlSrvc = AvrcpControllerService.getAvrcpControllerService();
@@ -384,7 +390,7 @@
     // Refresh the UI if we have a connected device and AVRCP is initialized.
     private synchronized void refreshInitialPlayingState() {
         if (mA2dpDevice == null) {
-            Log.d(TAG, "device " + mA2dpDevice);
+            if (DBG) Log.d(TAG, "device " + mA2dpDevice);
             return;
         }
 
@@ -395,10 +401,11 @@
         }
 
         if (mA2dpDevice != null && !mA2dpDevice.equals(devices.get(0))) {
-            Log.e(TAG, "A2dp device : " + mA2dpDevice + " avrcp device " + 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.
@@ -406,26 +413,28 @@
         playbackState = pbb.setActions(mTransportControlFlags).build();
 
         MediaMetadata mediaMetadata = mAvrcpCtrlSrvc.getMetaData(mA2dpDevice);
-        Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
+        if (VDBG) {
+            Log.d(TAG, "Media metadata " + mediaMetadata + " playback state " + playbackState);
+        }
         mSession.setMetadata(mAvrcpCtrlSrvc.getMetaData(mA2dpDevice));
         mSession.setPlaybackState(playbackState);
     }
 
     private void msgDeviceDisconnect(BluetoothDevice device) {
-        Log.d(TAG, "msgDeviceDisconnect");
+        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);
+            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)
+                PLAYBACK_SPEED)
                 .setActions(mTransportControlFlags)
                 .setErrorMessage(getString(R.string.bluetooth_disconnected));
         mSession.setPlaybackState(pbb.build());
@@ -438,7 +447,7 @@
     }
 
     private void msgTrack(PlaybackState pb, MediaMetadata mmd) {
-        Log.d(TAG, "msgTrack: playback: " + pb + " mmd: " + 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();
@@ -451,16 +460,16 @@
         if (prevMM != null) {
             String title = prevMM.getString(MediaMetadata.METADATA_KEY_TITLE);
             long trackLen = prevMM.getLong(MediaMetadata.METADATA_KEY_DURATION);
-            Log.d(TAG, "prev MM title " + title + " track len " + trackLen);
+            if (VDBG) Log.d(TAG, "prev MM title " + title + " track len " + trackLen);
         }
 
         if (mmd != null) {
-            Log.d(TAG, "msgTrack() mmd " + mmd.getDescription());
+            if (VDBG) Log.d(TAG, "msgTrack() mmd " + mmd.getDescription());
             mSession.setMetadata(mmd);
         }
 
         if (pb != null) {
-            Log.d(TAG, "msgTrack() playbackstate " + pb);
+            if (DBG) Log.d(TAG, "msgTrack() playbackstate " + pb);
             PlaybackState.Builder pbb = new PlaybackState.Builder(pb);
             pb = pbb.setActions(mTransportControlFlags).build();
             mSession.setPlaybackState(pb);
@@ -474,25 +483,25 @@
     }
 
     private synchronized void msgPassThru(int cmd) {
-        Log.d(TAG, "msgPassThru " + cmd);
+        if (DBG) Log.d(TAG, "msgPassThru " + cmd);
         if (mA2dpDevice == null) {
             // We should have already disconnected - ignore this message.
-            Log.e(TAG, "Already disconnected ignoring.");
+            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);
+        mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
+                AvrcpControllerService.KEY_STATE_PRESSED);
+        mAvrcpCtrlSrvc.sendPassThroughCmd(mA2dpDevice, cmd,
+                AvrcpControllerService.KEY_STATE_RELEASED);
     }
 
     private synchronized void msgGetPlayStatusNative() {
-        Log.d(TAG, "msgGetPlayStatusNative");
+        if (DBG) Log.d(TAG, "msgGetPlayStatusNative");
         if (mA2dpDevice == null) {
             // We should have already disconnected - ignore this message.
-            Log.e(TAG, "Already disconnected ignoring.");
+            Log.w(TAG, "Already disconnected ignoring.");
             return;
         }
 
@@ -501,11 +510,11 @@
     }
 
     private void msgDeviceBrowseConnect(BluetoothDevice device) {
-        Log.d(TAG, "msgDeviceBrowseConnect device " + 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);
+            Log.e(TAG, "Browse connected over different device a2dp " + mA2dpDevice + " browse "
+                    + device);
             return;
         }
         mBrowseConnected = true;
@@ -516,15 +525,15 @@
     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);
+                (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);
-        Log.d(TAG, "Parent: " + id + " Folder list: " + folderList);
+        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
@@ -540,11 +549,11 @@
     }
 
     private void msgDeviceBrowseDisconnect(BluetoothDevice device) {
-        Log.d(TAG, "msgDeviceBrowseDisconnect device " + 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);
+            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
index da19b32..1d4810a 100644
--- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java
@@ -18,22 +18,21 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.bluetooth.BluetoothAvrcp;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.media.session.MediaSession.QueueItem;
 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.btservice.ProfileService;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
 
 import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 /*************************************************************************************************
  * Provides functionality required for Addressed Media Player, like Now Playing List related
@@ -42,17 +41,17 @@
  ************************************************************************************************/
 
 public class AddressedMediaPlayer {
-    static private final String TAG = "AddressedMediaPlayer";
-    static private final Boolean DEBUG = false;
+    private static final String TAG = "AddressedMediaPlayer";
+    private static final Boolean DEBUG = false;
 
-    static private final long SINGLE_QID = 1;
-    static private final String UNKNOWN_TITLE = "(unknown)";
+    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;
-    private @NonNull List<MediaSession.QueueItem> mNowPlayingList;
+    @NonNull private List<MediaSession.QueueItem> mNowPlayingList;
 
     private final List<MediaSession.QueueItem> mEmptyNowPlayingList;
 
@@ -66,7 +65,9 @@
     }
 
     void cleanup() {
-        if (DEBUG) Log.v(TAG, "cleanup");
+        if (DEBUG) {
+            Log.v(TAG, "cleanup");
+        }
         mNowPlayingList = mEmptyNowPlayingList;
         mMediaInterface = null;
         mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID;
@@ -98,13 +99,14 @@
         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);
+                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));
+        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);
@@ -120,11 +122,13 @@
      */
     @NonNull
     List<MediaSession.QueueItem> updateNowPlayingList(@Nullable MediaController mediaController) {
-        if (mediaController == null) return mEmptyNowPlayingList;
+        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");
+                    + ", 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.
@@ -152,15 +156,21 @@
             items.add(current);
         }
 
-        if (!items.equals(mNowPlayingList)) sendNowPlayingListChanged();
+        if (!items.equals(mNowPlayingList)) {
+            sendNowPlayingListChanged();
+        }
         mNowPlayingList = items;
 
         return mNowPlayingList;
     }
 
     private void sendNowPlayingListChanged() {
-        if (mMediaInterface == null) return;
-        if (DEBUG) Log.d(TAG, "sendNowPlayingListChanged()");
+        if (mMediaInterface == null) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "sendNowPlayingListChanged()");
+        }
         mMediaInterface.nowPlayingChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED);
     }
 
@@ -171,19 +181,32 @@
         }
 
         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));
+        if (bundle == null) {
+            bundle = new Bundle();
         }
 
-        String[] longKeys = {MediaMetadata.METADATA_KEY_TRACK_NUMBER,
-                MediaMetadata.METADATA_KEY_NUM_TRACKS, MediaMetadata.METADATA_KEY_DURATION};
+        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));
+            if (!bundle.containsKey(key)) {
+                bundle.putLong(key, metadata.getLong(key));
+            }
         }
         return bundle;
     }
@@ -210,7 +233,9 @@
 
         for (MediaSession.QueueItem item : items) {
             if (qid == item.getQueueId()) {
-                if (DEBUG) Log.d(TAG, "Skipping to ID " + qid);
+                if (DEBUG) {
+                    Log.d(TAG, "Skipping to ID " + qid);
+                }
                 mediaControllerCntrl.skipToQueueItem(qid);
                 mMediaInterface.playItemRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR);
                 return;
@@ -223,7 +248,9 @@
 
     void getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController) {
         List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController);
-        if (DEBUG) Log.d(TAG, "getTotalNumOfItems: " + items.size() + " items.");
+        if (DEBUG) {
+            Log.d(TAG, "getTotalNumOfItems: " + items.size() + " items.");
+        }
         mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size());
     }
 
@@ -240,10 +267,15 @@
      * helper method to check if startItem and endItem index is with range of
      * MediaItem list. (Resultset containing all items in current path)
      */
-    private @Nullable 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;
+    @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));
@@ -267,25 +299,27 @@
     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);
+        if (DEBUG) {
+            Log.d(TAG,
+                    "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem);
+        }
 
-        List<MediaSession.QueueItem> result_items = getQueueSubset(items, startItem, endItem);
+        List<MediaSession.QueueItem> resultItems = getQueueSubset(items, startItem, endItem);
         /* check for index out of bound errors */
-        if (result_items == null) {
-            Log.w(TAG, "getFolderItemsFilterAttr: result_items is empty");
+        if (resultItems == null) {
+            Log.w(TAG, "getFolderItemsFilterAttr: resultItems is empty");
             mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_INV_RANGE, null);
             return;
         }
 
-        FolderItemsData folderDataNative = new FolderItemsData(result_items.size());
+        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 < result_items.size(); itemIndex++) {
-            MediaSession.QueueItem item = result_items.get(itemIndex);
+        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();
@@ -336,30 +370,36 @@
         /* 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++)
+            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)
+        for (int attrIndex = 0; attrIndex < folderDataNative.mAttributesNum.length; attrIndex++) {
+            if (DEBUG) {
                 Log.d(TAG, "folderDataNative.mAttributesNum"
-                                + folderDataNative.mAttributesNum[attrIndex] + " attrIndex "
-                                + attrIndex);
+                        + 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);
+        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) {
+    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");
+            if (DEBUG) {
+                Log.d(TAG, "getAttrValue received null item");
+            }
             return null;
         }
         try {
@@ -368,10 +408,13 @@
             boolean isCurrentTrack = item.getQueueId() == getActiveQueueItemId(mediaController);
             MediaMetadata data = null;
             if (isCurrentTrack) {
-                if (DEBUG) Log.d(TAG, "getAttrValue: item is active, using current data");
+                if (DEBUG) {
+                    Log.d(TAG, "getAttrValue: item is active, using current data");
+                }
                 data = mediaController.getMetadata();
-                if (data == null)
+                if (data == null) {
                     Log.e(TAG, "getMetadata didn't give us any metadata for the current track");
+                }
             }
 
             if (data == null) {
@@ -382,7 +425,9 @@
 
             extras = fillBundle(data, extras);
 
-            if (DEBUG) Log.d(TAG, "getAttrValue: item " + item + " : " + desc);
+            if (DEBUG) {
+                Log.d(TAG, "getAttrValue: item " + item + " : " + desc);
+            }
             switch (attr) {
                 case AvrcpConstants.ATTRID_TITLE:
                     /* Title is mandatory attribute */
@@ -436,7 +481,9 @@
                 return null;
             }
         }
-        if (DEBUG) Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
+        if (DEBUG) {
+            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + ", attr id:" + attr);
+        }
         return attrValue;
     }
 
@@ -460,15 +507,18 @@
             } else {
                 /* get only the requested attribute ids from the request */
                 for (int idx = 0; idx < mItemAttrReqObj.mNumAttr; idx++) {
-                    if (DEBUG)
+                    if (DEBUG) {
                         Log.d(TAG, "getItemAttrFilterAttr: attr id[" + idx + "] :"
-                                        + mItemAttrReqObj.mAttrIDs[idx]);
+                                + mItemAttrReqObj.mAttrIDs[idx]);
+                    }
                     attrTempId.add(mItemAttrReqObj.mAttrIDs[idx]);
                 }
             }
         }
 
-        if (DEBUG) Log.d(TAG, "getItemAttrFilterAttr: attr id list size:" + attrTempId.size());
+        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 */
@@ -483,8 +533,9 @@
         if (mItemAttrReqObj.mNumAttr != AvrcpConstants.NUM_ATTR_NONE) {
             attrIds = new int[attrId.size()];
 
-            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++)
+            for (int attrIndex = 0; attrIndex < attrId.size(); attrIndex++) {
                 attrIds[attrIndex] = attrId.get(attrIndex);
+            }
 
             attrValues = attrArray.toArray(new String[attrId.size()]);
 
@@ -496,15 +547,22 @@
     }
 
     private long getActiveQueueItemId(@Nullable MediaController controller) {
-        if (controller == null) return MediaSession.QueueItem.UNKNOWN_ID;
+        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)
+                || state.getState() == PlaybackState.STATE_NONE) {
             return MediaSession.QueueItem.UNKNOWN_ID;
+        }
         long qid = state.getActiveQueueItemId();
-        if (qid != MediaSession.QueueItem.UNKNOWN_ID) return qid;
+        if (qid != MediaSession.QueueItem.UNKNOWN_ID) {
+            return qid;
+        }
         // Check if we're presenting a "one item queue"
-        if (controller.getMetadata() != null) return SINGLE_QID;
+        if (controller.getMetadata() != null) {
+            return SINGLE_QID;
+        }
         return MediaSession.QueueItem.UNKNOWN_ID;
     }
 
@@ -536,8 +594,8 @@
         long currentQueueId = getActiveQueueItemId(mediaController);
         for (MediaSession.QueueItem item : mNowPlayingList) {
             long itemId = item.getQueueId();
-            ProfileService.println(
-                    sb, (itemId == currentQueueId ? "*" : " ") + displayMediaItem(item));
+            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
index a00502c..e30ad41 100644
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -20,25 +20,22 @@
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAvrcp;
-import android.bluetooth.BluetoothDevice;
 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.content.SharedPreferences;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
-import android.media.browse.MediaBrowser;
 import android.media.session.MediaSession;
-import android.media.session.MediaSession.QueueItem;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
@@ -51,9 +48,9 @@
 import android.util.Log;
 import android.view.KeyEvent;
 
-import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -72,23 +69,23 @@
  ******************************************************************************/
 
 public final class Avrcp {
-    private static final boolean DEBUG = true;
+    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 AvrcpMessageHandler mHandler;
+    private volatile AvrcpMessageHandler mHandler;
     private Handler mAudioManagerPlaybackHandler;
     private AudioManagerPlaybackListener mAudioManagerPlaybackCb;
     private MediaSessionManager mMediaSessionManager;
-    private @Nullable MediaController mMediaController;
+    @Nullable private MediaController mMediaController;
     private MediaControllerListener mMediaControllerCb;
     private MediaAttributes mMediaAttributes;
     private long mLastQueueId;
     private PackageManager mPackageManager;
     private int mTransportControlFlags;
-    private @NonNull PlaybackState mCurrentPlayState;
+    @NonNull private PlaybackState mCurrentPlayState;
     private int mA2dpState;
     private boolean mAudioManagerIsPlaying;
     private int mPlayStatusChangedNT;
@@ -118,7 +115,6 @@
     private int mLastDirection;
     private final int mVolumeStep;
     private final int mAudioStreamMax;
-    private boolean mVolCmdAdjustInProgress;
     private boolean mVolCmdSetInProgress;
     private int mAbsVolRetryTimes;
 
@@ -163,7 +159,6 @@
 
     /* other AVRC messages */
     private static final int MSG_PLAY_INTERVAL_TIMEOUT = 14;
-    private static final int MSG_ADJUST_VOLUME = 15;
     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;
@@ -195,7 +190,7 @@
     private final BroadcastReceiver mBootReceiver = new AvrcpServiceBootReceiver();
 
     /* Recording passthrough key dispatches */
-    static private final int PASSTHROUGH_LOG_MAX_SIZE = DEBUG ? 50 : 10;
+    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
@@ -206,20 +201,27 @@
         private String mPackage;
         private KeyEvent mEvent;
 
-        public MediaKeyLog(long time, KeyEvent event) {
+        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;
+            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));
@@ -241,7 +243,8 @@
     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();
+        mCurrentPlayState =
+                new PlaybackState.Builder().setState(PlaybackState.STATE_NONE, -1L, 0.0f).build();
         mReportedPlayStatus = PLAYSTATUS_ERROR;
         mA2dpState = BluetoothA2dp.STATE_NOT_PLAYING;
         mAudioManagerIsPlaying = false;
@@ -259,7 +262,6 @@
         mInitialRemoteVolume = -1;
         mLastRemoteVolume = -1;
         mLastDirection = 0;
-        mVolCmdAdjustInProgress = false;
         mVolCmdSetInProgress = false;
         mAbsVolRetryTimes = 0;
         mLocalVolume = -1;
@@ -275,21 +277,22 @@
 
         initNative();
 
-        mMediaSessionManager = (MediaSessionManager) context.getSystemService(
-            Context.MEDIA_SESSION_SERVICE);
+        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);
+        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);
+            mAbsVolThreshold =
+                    resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold);
 
-            // Update the threshold if the threshold_percent is valid
-            int threshold_percent =
+            // Update the threshold if the thresholdPercent is valid
+            int thresholdPercent =
                     resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold_percent);
-            if (threshold_percent >= 0 && threshold_percent <= 100) {
-                mAbsVolThreshold = (threshold_percent * mAudioStreamMax) / 100;
+            if (thresholdPercent >= 0 && thresholdPercent <= 100) {
+                mAbsVolThreshold = (thresholdPercent * mAudioStreamMax) / 100;
             }
         }
 
@@ -339,28 +342,36 @@
 
         UserManager manager = UserManager.get(mContext);
         if (manager == null || manager.isUserUnlocked()) {
-            if (DEBUG) Log.d(TAG, "User already unlocked, initializing player lists");
+            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);
+        mAudioManager.registerAudioPlaybackCallback(mAudioManagerPlaybackCb,
+                mAudioManagerPlaybackHandler);
     }
 
     public static Avrcp make(Context context) {
-        if (DEBUG) Log.v(TAG, "make");
+        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 (DEBUG) {
+            Log.d(TAG, "doQuit");
+        }
         if (mAudioManager != null) {
             mAudioManager.unregisterAudioPlaybackCallback(mAudioManagerPlaybackCb);
         }
-        if (mMediaController != null) mMediaController.unregisterCallback(mMediaControllerCb);
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mMediaControllerCb);
+        }
         if (mMediaSessionManager != null) {
             mMediaSessionManager.setCallback(null, null);
             mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveSessionListener);
@@ -369,12 +380,12 @@
         mAudioManagerPlaybackHandler.removeCallbacksAndMessages(null);
         mHandler.removeCallbacksAndMessages(null);
         Looper looper = mHandler.getLooper();
+        mHandler = null;
         if (looper != null) {
-            looper.quit();
+            looper.quitSafely();
         }
 
         mAudioManagerPlaybackHandler = null;
-        mHandler = null;
         mContext.unregisterReceiver(mAvrcpReceiver);
         mContext.unregisterReceiver(mBootReceiver);
 
@@ -383,10 +394,13 @@
     }
 
     public void cleanup() {
-        if (DEBUG) Log.d(TAG, "cleanup");
+        if (DEBUG) {
+            Log.d(TAG, "cleanup");
+        }
         cleanupNative();
-        if (mVolumeMapping != null)
+        if (mVolumeMapping != null) {
             mVolumeMapping.clear();
+        }
     }
 
     private class AudioManagerPlaybackListener extends AudioManager.AudioPlaybackCallback {
@@ -396,16 +410,17 @@
             boolean isPlaying = false;
             for (AudioPlaybackConfiguration config : configs) {
                 if (DEBUG) {
-                    Log.d(TAG,
-                            "AudioManager Player: "
-                                    + AudioPlaybackConfiguration.toLogFriendlyString(config));
+                    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 (DEBUG) {
+                Log.d(TAG, "AudioManager isPlaying: " + isPlaying);
+            }
             if (mAudioManagerIsPlaying != isPlaying) {
                 mAudioManagerIsPlaying = isPlaying;
                 updateCurrentMediaState();
@@ -416,12 +431,17 @@
     private class MediaControllerListener extends MediaController.Callback {
         @Override
         public void onMetadataChanged(MediaMetadata metadata) {
-            if (DEBUG) Log.v(TAG, "onMetadataChanged");
+            if (DEBUG) {
+                Log.v(TAG, "onMetadataChanged");
+            }
             updateCurrentMediaState();
         }
+
         @Override
         public synchronized void onPlaybackStateChanged(PlaybackState state) {
-            if (DEBUG) Log.v(TAG, "onPlaybackStateChanged: state " + state.toString());
+            if (DEBUG) {
+                Log.v(TAG, "onPlaybackStateChanged: state " + state.toString());
+            }
 
             updateCurrentMediaState();
         }
@@ -430,8 +450,9 @@
         public void onSessionDestroyed() {
             Log.v(TAG, "MediaController session destroyed");
             synchronized (Avrcp.this) {
-                if (mMediaController != null)
+                if (mMediaController != null) {
                     removeMediaController(mMediaController.getWrappedInstance());
+                }
             }
         }
 
@@ -442,8 +463,15 @@
                 return;
             }
 
-            Log.v(TAG, "onQueueChanged: NowPlaying list changed, Queue Size = "+ queue.size());
-            mHandler.sendEmptyMessage(MSG_NOW_PLAYING_CHANGED_RSP);
+            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);
         }
     }
 
@@ -456,388 +484,338 @@
         @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] + "] ");
+                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);
                     }
-                }
-                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");
+                    mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported());
+                    mLastLocalVolume = -1;
+                    mRemoteVolume = -1;
+                    mLocalVolume = -1;
+                    mInitialRemoteVolume = -1;
+                    mAddress = address;
+                    if (mVolumeMapping != null) {
+                        mVolumeMapping.clear();
+                    }
                     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);
 
-                boolean volAdj = false;
-                if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
-                    if (mVolCmdAdjustInProgress == false && mVolCmdSetInProgress == false) {
-                        Log.e(TAG, "Unsolicited response, ignored");
-                        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);
                     }
-                    removeMessages(MSG_ABS_VOL_TIMEOUT);
-
-                    volAdj = mVolCmdAdjustInProgress;
-                    mVolCmdAdjustInProgress = false;
-                    mVolCmdSetInProgress = false;
-                    mAbsVolRetryTimes = 0;
+                    getPlayStatusRspNative(address, btstate, length, position);
+                    break;
                 }
 
-                // 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 = mHandler.obtainMessage(MSG_SET_ABSOLUTE_VOLUME, mAbsVolThreshold , 0);
-                        mHandler.sendMessage(msg1);
-                        mRemoteVolume = absVol;
-                        mLocalVolume = volIndex;
-                        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;
                 }
 
-                if (mLocalVolume != volIndex && (msg.arg2 == AVRC_RSP_ACCEPT ||
-                                                 msg.arg2 == AVRC_RSP_CHANGED ||
-                                                 msg.arg2 == AVRC_RSP_INTERIM)) {
+                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) {
+                        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;
+                                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");
                     }
-                    // remember the remote volume value, as it's the one supported by remote
-                    if (volAdj) {
-                        synchronized (mVolumeMapping) {
-                            mVolumeMapping.put(volIndex, (int) absVol);
-                            if (DEBUG) Log.v(TAG, "remember volume mapping " +volIndex+ "-"+absVol);
+                    break;
+
+                case MSG_SET_ABSOLUTE_VOLUME:
+                    if (!isAbsoluteVolumeSupported()) {
+                        if (DEBUG) {
+                            Log.v(TAG, "ignore MSG_SET_ABSOLUTE_VOLUME");
                         }
-                    }
-
-                    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");
-                } else if (volAdj && mLastRemoteVolume > 0 && mLastRemoteVolume < AVRCP_MAX_VOL &&
-                        mLocalVolume == volIndex &&
-                        (msg.arg2 == AVRC_RSP_ACCEPT)) {
-                    /* oops, the volume is still same, remote does not like the value
-                     * retry a volume one step up/down */
-                    if (DEBUG) Log.d(TAG, "Remote device didn't tune volume, let's try one more step.");
-                    int retry_volume = Math.min(AVRCP_MAX_VOL,
-                            Math.max(0, mLastRemoteVolume + mLastDirection));
-                    if (setVolumeNative(retry_volume)) {
-                        mLastRemoteVolume = retry_volume;
-                        sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
-                        mVolCmdAdjustInProgress = true;
-                    }
-                }
-                break;
-
-            case MSG_ADJUST_VOLUME:
-                if (!isAbsoluteVolumeSupported()) {
-                    if (DEBUG) Log.v(TAG, "ignore MSG_ADJUST_VOLUME");
-                    break;
-                }
-
-                if (DEBUG) Log.d(TAG, "MSG_ADJUST_VOLUME: direction=" + msg.arg1);
-
-                if (mVolCmdAdjustInProgress || 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) {
-                    Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it.");
-                    blackListCurrentDevice();
-                    break;
-                }
-
-                // Wait on verification on volume from device, before changing the volume.
-                if (mRemoteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) {
-                    int setVol = -1;
-                    int targetVolIndex = -1;
-                    if (mLocalVolume == 0 && msg.arg1 == -1) {
-                        if (DEBUG) Log.w(TAG, "No need to Vol down from 0.");
-                        break;
-                    }
-                    if (mLocalVolume == mAudioStreamMax && msg.arg1 == 1) {
-                        if (DEBUG) Log.w(TAG, "No need to Vol up from max.");
                         break;
                     }
 
-                    targetVolIndex = mLocalVolume + msg.arg1;
-                    if (DEBUG) Log.d(TAG, "Adjusting volume to  " + targetVolIndex);
-
-                    Integer i;
-                    synchronized (mVolumeMapping) {
-                        i = mVolumeMapping.get(targetVolIndex);
+                    if (DEBUG) {
+                        Log.v(TAG, "MSG_SET_ABSOLUTE_VOLUME");
                     }
 
-                    if (i != null) {
-                        /* if we already know this volume mapping, use it */
-                        setVol = i.byteValue();
-                        if (setVol == mRemoteVolume) {
-                            if (DEBUG) Log.d(TAG, "got same volume from mapping for " + targetVolIndex + ", ignore.");
-                            setVol = -1;
+                    if (mVolCmdSetInProgress) {
+                        if (DEBUG) {
+                            Log.w(TAG, "There is already a volume command in progress.");
                         }
-                        if (DEBUG) Log.d(TAG, "set volume from mapping " + targetVolIndex + "-" + setVol);
+                        break;
                     }
 
-                    if (setVol == -1) {
-                        /* otherwise use phone steps */
-                        setVol = Math.min(AVRCP_MAX_VOL,
-                                convertToAvrcpVolume(Math.max(0, targetVolIndex)));
-                        if (DEBUG) Log.d(TAG, "set volume from local volume "+ targetVolIndex+"-"+ setVol);
+                    // 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;
                     }
 
-                    if (setVolumeNative(setVol)) {
-                        sendMessageDelayed(obtainMessage(MSG_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
-                        mVolCmdAdjustInProgress = true;
-                        mLastDirection = msg.arg1;
-                        mLastRemoteVolume = setVol;
-                        mLastLocalVolume = targetVolIndex;
-                    } else {
-                         if (DEBUG) Log.d(TAG, "setVolumeNative failed");
+                    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);
                     }
-                } else {
-                    Log.e(TAG, "Unknown direction in MSG_ADJUST_VOLUME");
-                }
-                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 || mVolCmdAdjustInProgress) {
-                    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();
-                    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.");
-                mVolCmdAdjustInProgress = false;
-                mVolCmdSetInProgress = false;
-                if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) {
-                    mAbsVolRetryTimes = 0;
-                    /* too many volume change failures, black list the device */
-                    blackListCurrentDevice();
-                } else {
-                    mAbsVolRetryTimes += 1;
-                    if (setVolumeNative(mLastRemoteVolume)) {
+                    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;
                 }
-                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_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_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);
+                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;
                 }
-                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);
+                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;
                 }
-                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_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;
 
-            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;
+                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();
+        PlaybackState newState = new PlaybackState.Builder().setState(PlaybackState.STATE_NONE,
+                PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0.0f).build();
         synchronized (this) {
             PlaybackState controllerState = null;
             if (mMediaController != null) {
@@ -855,9 +833,9 @@
             // 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())) {
+            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();
@@ -882,10 +860,12 @@
 
         if (DEBUG) {
             Log.v(TAG, "updatePlaybackState (" + mPlayStatusChangedNT + "): " + mReportedPlayStatus
-                            + "➡" + newPlayStatus + "(" + newState + ")");
+                    + "➡" + newPlayStatus + "(" + newState + ")");
         }
 
-        if (newState != null) mCurrentPlayState = newState;
+        if (newState != null) {
+            mCurrentPlayState = newState;
+        }
 
         return mCurrentPlayState;
     }
@@ -901,14 +881,14 @@
     }
 
     class MediaAttributes {
-        private boolean exists;
-        private String title;
-        private String artistName;
-        private String albumName;
-        private String mediaNumber;
-        private String mediaTotalNumber;
-        private String genre;
-        private long playingTimeMs;
+        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;
@@ -919,75 +899,84 @@
         private static final int ATTR_PLAYING_TIME_MS = 7;
 
 
-        public MediaAttributes(MediaMetadata data) {
-            exists = data != null;
-            if (!exists)
+        MediaAttributes(MediaMetadata data) {
+            mExists = data != null;
+            if (!mExists) {
                 return;
+            }
 
-            artistName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ARTIST));
-            albumName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ALBUM));
-            mediaNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
-            mediaTotalNumber = longStringOrBlank(data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
-            genre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE));
-            playingTimeMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION);
+            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.
-            title = data.getString(MediaMetadata.METADATA_KEY_TITLE);
+            mTitle = data.getString(MediaMetadata.METADATA_KEY_TITLE);
 
-            if (title == null) {
+            if (mTitle == null) {
                 MediaDescription desc = data.getDescription();
                 if (desc != null) {
                     CharSequence val = desc.getDescription();
-                    if (val != null)
-                        title = val.toString();
+                    if (val != null) {
+                        mTitle = val.toString();
+                    }
                 }
             }
 
-            if (title == null)
-                title = new String();
+            if (mTitle == null) {
+                mTitle = new String();
+            }
         }
 
         public long getLength() {
-            if (!exists) return 0L;
-            return playingTimeMs;
+            if (!mExists) {
+                return 0L;
+            }
+            return mPlayingTimeMs;
         }
 
         public boolean equals(MediaAttributes other) {
-            if (other == null)
+            if (other == null) {
                 return false;
+            }
 
-            if (exists != other.exists)
+            if (mExists != other.mExists) {
                 return false;
+            }
 
-            if (exists == false)
+            if (!mExists) {
                 return true;
+            }
 
-            return (title.equals(other.title)) && (artistName.equals(other.artistName))
-                    && (albumName.equals(other.albumName))
-                    && (mediaNumber.equals(other.mediaNumber))
-                    && (mediaTotalNumber.equals(other.mediaTotalNumber))
-                    && (genre.equals(other.genre)) && (playingTimeMs == other.playingTimeMs);
+            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 (!exists)
+            if (!mExists) {
                 return new String();
+            }
 
             switch (attrId) {
                 case ATTR_TITLE:
-                    return title;
+                    return mTitle;
                 case ATTR_ARTIST_NAME:
-                    return artistName;
+                    return mArtistName;
                 case ATTR_ALBUM_NAME:
-                    return albumName;
+                    return mAlbumName;
                 case ATTR_MEDIA_NUMBER:
-                    return mediaNumber;
+                    return mMediaNumber;
                 case ATTR_MEDIA_TOTAL_NUMBER:
-                    return mediaTotalNumber;
+                    return mMediaTotalNumber;
                 case ATTR_GENRE:
-                    return genre;
+                    return mGenre;
                 case ATTR_PLAYING_TIME_MS:
-                    return Long.toString(playingTimeMs);
+                    return Long.toString(mPlayingTimeMs);
                 default:
                     return new String();
             }
@@ -1001,25 +990,25 @@
             return s == null ? new String() : s.toString();
         }
 
+        @Override
         public String toString() {
-            if (!exists) {
+            if (!mExists) {
                 return "[MediaAttributes: none]";
             }
 
-            return "[MediaAttributes: " + title + " - " + albumName + " by " + artistName + " ("
-                    + playingTimeMs + " " + mediaNumber + "/" + mediaTotalNumber + ") " + genre
+            return "[MediaAttributes: " + mTitle + " - " + mAlbumName + " by " + mArtistName + " ("
+                    + mPlayingTimeMs + " " + mMediaNumber + "/" + mMediaTotalNumber + ") " + mGenre
                     + "]";
         }
 
         public String toRedactedString() {
-            if (!exists) {
+            if (!mExists) {
                 return "[MediaAttributes: none]";
             }
 
-            return "[MediaAttributes: " + Utils.ellipsize(title) + " - "
-                    + Utils.ellipsize(albumName) + " by " + Utils.ellipsize(artistName) + " ("
-                    + playingTimeMs + " " + mediaNumber + "/" + mediaTotalNumber + ") " + genre
-                    + "]";
+            return "[MediaAttributes: " + Utils.ellipsize(mTitle) + " - " + Utils.ellipsize(
+                    mAlbumName) + " by " + Utils.ellipsize(mArtistName) + " (" + mPlayingTimeMs
+                    + " " + mMediaNumber + "/" + mMediaTotalNumber + ") " + mGenre + "]";
         }
     }
 
@@ -1041,10 +1030,14 @@
         if (newState.getState() != PlaybackState.STATE_BUFFERING
                 && newState.getState() != PlaybackState.STATE_NONE) {
             long newQueueId = MediaSession.QueueItem.UNKNOWN_ID;
-            if (newState != null) newQueueId = newState.getActiveQueueItemId();
-            Log.v(TAG, "Media update: id " + mLastQueueId + "➡" + newQueueId + "? "
-                            + currentAttributes.toRedactedString() + " : "
-                            + mMediaAttributes.toRedactedString());
+            if (newState != null) {
+                newQueueId = newState.getActiveQueueItemId();
+            }
+            if (DEBUG) {
+                Log.v(TAG,
+                        "Media update: id " + mLastQueueId + "➡" + newQueueId + "? " + currentAttributes
+                                .toRedactedString() + " : " + mMediaAttributes.toRedactedString());
+            }
 
             if (mAvailablePlayerViewChanged) {
                 registerNotificationRspAvalPlayerChangedNative(
@@ -1073,7 +1066,9 @@
             // Dont send now playing list changed if the player doesn't support browsing
             MediaPlayerInfo info = getAddressedPlayerInfo();
             if (info != null && info.isBrowseSupported()) {
-                Log.v(TAG, "Check if NowPlayingList is updated");
+                if (DEBUG) {
+                    Log.v(TAG, "Check if NowPlayingList is updated");
+                }
                 mAddressedMediaPlayer.updateNowPlayingList(mMediaController);
             }
 
@@ -1095,11 +1090,12 @@
         }
 
         // still send the updated play state if the playback state is none or buffering
-        Log.e(TAG,
-                "play status change " + mReportedPlayStatus + "➡" + newPlayStatus
-                        + " mPlayStatusChangedNT: " + mPlayStatusChangedNT);
-        if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM
-                || (mReportedPlayStatus != newPlayStatus)) {
+        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);
         }
 
@@ -1107,15 +1103,27 @@
     }
 
     private void getRcFeaturesRequestFromNative(byte[] address, int features) {
-        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_RC_FEATURES, features, 0,
+        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));
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
     private void getPlayStatusRequestFromNative(byte[] address) {
-        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_PLAY_STATUS);
+        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;
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
     private void getElementAttrRequestFromNative(byte[] address, byte numAttr, int[] attrs) {
@@ -1126,10 +1134,17 @@
         mHandler.sendMessage(msg);
     }
 
-    private void registerNotificationRequestFromNative(byte[] address,int eventId, int param) {
-        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_REGISTER_NOTIFICATION, eventId, param);
+    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;
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
     private void processRegisterNotification(byte[] address, int eventId, int param) {
@@ -1154,59 +1169,81 @@
 
             case EVT_AVBL_PLAYERS_CHANGED:
                 /* Notify remote available players changed */
-                if (DEBUG) Log.d(TAG, "Available Players notification enabled");
+                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");
+                if (DEBUG) {
+                    Log.d(TAG, "Addressed Player notification enabled");
+                }
                 registerNotificationRspAddrPlayerChangedNative(
-                        AvrcpConstants.NOTIFICATION_TYPE_INTERIM,
-                        mCurrAddrPlayerID, sUIDCounter);
+                        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);
+                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");
+                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!");
+                    Log.e(TAG, "EVENT_NOW_PLAYING_CONTENT_CHANGED: "
+                            + "registerNotificationRspNowPlayingChangedNative for Interim rsp "
+                            + "failed!");
                 }
                 break;
         }
     }
 
     private void handlePassthroughCmdRequestFromNative(byte[] address, int id, int keyState) {
-        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_PASS_THROUGH, id, keyState);
-        mHandler.sendMessage(msg);
+        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.");
+            if (DEBUG) {
+                Log.d(TAG, "sendTrackChangedRsp: Not registered or registering.");
+            }
             return;
         }
 
         mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
-        if (registering) mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM;
+        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.exists) track = AvrcpConstants.NO_TRACK_SELECTED;
+            if (!mMediaAttributes.mExists) {
+                track = AvrcpConstants.NO_TRACK_SELECTED;
+            }
             registerNotificationRspTrackChangeNative(mTrackChangedNT, track);
             return;
         }
@@ -1233,7 +1270,9 @@
     }
 
     private boolean isPlayingState(@Nullable PlaybackState state) {
-        if (state == null) return false;
+        if (state == null) {
+            return false;
+        }
         return (state != null) && (state.getState() == PlaybackState.STATE_PLAYING);
     }
 
@@ -1245,7 +1284,15 @@
      */
     private void sendPlayPosNotificationRsp(boolean requested) {
         if (!requested && mPlayPosChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) {
-            if (DEBUG) Log.d(TAG, "sendPlayPosNotificationRsp: Not registered or requesting.");
+            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;
         }
 
@@ -1260,12 +1307,17 @@
         if (DEBUG) {
             debugLine += "(" + requested + ") " + mPrevPosMs + " <=? " + playPositionMs + " <=? "
                     + mNextPosMs;
-            if (isPlayingState(mCurrentPlayState)) debugLine += " Playing";
+            if (isPlayingState(mCurrentPlayState)) {
+                debugLine += " Playing";
+            }
             debugLine += " State: " + mCurrentPlayState.getState();
         }
-        if (requested || ((mLastReportedPosition != playPositionMs) &&
-                (playPositionMs >= mNextPosMs) || (playPositionMs <= mPrevPosMs))) {
-            if (!requested) mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
+        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) {
@@ -1277,17 +1329,22 @@
             }
         }
 
-        mHandler.removeMessages(MSG_PLAY_INTERVAL_TIMEOUT);
-        if (mPlayPosChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM && isPlayingState(mCurrentPlayState)) {
-            Message msg = mHandler.obtainMessage(MSG_PLAY_INTERVAL_TIMEOUT);
+        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";
-            mHandler.sendMessageDelayed(msg, delay);
+            if (DEBUG) {
+                debugLine += " Timeout " + delay + "ms";
+            }
+            handler.sendMessageDelayed(msg, delay);
         }
-        if (DEBUG) Log.d(TAG, debugLine);
+        if (DEBUG) {
+            Log.d(TAG, debugLine);
+        }
     }
 
     /**
@@ -1302,20 +1359,22 @@
      * We get this call from AudioService. This will send a message to our handler object,
      * requesting our handler to call setVolumeNative()
      */
-    public void adjustVolume(int direction) {
-        Message msg = mHandler.obtainMessage(MSG_ADJUST_VOLUME, direction, 0);
-        mHandler.sendMessage(msg);
-    }
-
     public void setAbsoluteVolume(int volume) {
         if (volume == mLocalVolume) {
-            if (DEBUG) Log.v(TAG, "setAbsoluteVolume is setting same index, ignore "+volume);
+            if (DEBUG) {
+                Log.v(TAG, "setAbsoluteVolume is setting same index, ignore " + volume);
+            }
             return;
         }
 
-        mHandler.removeMessages(MSG_ADJUST_VOLUME);
-        Message msg = mHandler.obtainMessage(MSG_SET_ABSOLUTE_VOLUME, volume, 0);
-        mHandler.sendMessage(msg);
+        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
@@ -1326,53 +1385,88 @@
      * AudioService to update the UI
      */
     private void volumeChangeRequestFromNative(byte[] address, int volume, int ctype) {
-        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_VOLUME_CHANGE, volume, 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);
+        data.putByteArray("BdAddress", address);
         msg.setData(data);
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
-    private void getFolderItemsRequestFromNative(
-            byte[] address, byte scope, long startItem, long endItem, byte numAttr, int[] attrIds) {
+    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 = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_FOLDER_ITEMS, 0, 0);
+        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;
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
     private void setAddressedPlayerRequestFromNative(byte[] address, int playerId) {
-        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_SET_ADDR_PLAYER, playerId, 0);
+        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;
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
     private void setBrowsedPlayerRequestFromNative(byte[] address, int playerId) {
-        Message msg = mHandler.obtainMessage(MSG_NATIVE_REQ_SET_BR_PLAYER, playerId, 0);
+        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;
-        mHandler.sendMessage(msg);
+        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 = mHandler.obtainMessage(MSG_NATIVE_REQ_CHANGE_PATH);
-        data.putByteArray("BdAddress" , address);
-        data.putByteArray("folderUid" , folderUid);
-        data.putByte("direction" , direction);
+        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_CHANGE_PATH);
+        data.putByteArray("BdAddress", address);
+        data.putByteArray("folderUid", folderUid);
+        data.putByte("direction", direction);
         msg.setData(data);
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
-    private void getItemAttrRequestFromNative(byte[] address, byte scope, byte[] itemUid, int uidCounter,
-            byte numAttr, int[] attrs) {
+    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 = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_ITEM_ATTR);
+        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;
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
     private void searchRequestFromNative(byte[] address, int charsetId, byte[] searchStr) {
@@ -1382,70 +1476,88 @@
     }
 
     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 = mHandler.obtainMessage(MSG_NATIVE_REQ_PLAY_ITEM);
-        data.putByteArray("BdAddress" , address);
-        data.putByteArray("uid" , uid);
-        data.putInt("uidCounter" , uidCounter);
-        data.putByte("scope" , scope);
+        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);
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
-    private void addToPlayListRequestFromNative(byte[] address, byte scope, byte[] uid, int uidCounter) {
+    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 = mHandler.obtainMessage(MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS);
+        Message msg = handler.obtainMessage(MSG_NATIVE_REQ_GET_TOTAL_NUM_OF_ITEMS);
         msg.arg1 = scope;
         msg.obj = address;
-        mHandler.sendMessage(msg);
+        handler.sendMessage(msg);
     }
 
     private void notifyVolumeChanged(int volume) {
         mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
-                      AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_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);
+        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);
+        return (int) Math.ceil((double) volume * AVRCP_MAX_VOL / mAudioStreamMax);
     }
 
-    private void blackListCurrentDevice() {
+    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 pref =
+                mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = pref.edit();
-        editor.putBoolean(mAddress, true);
+
+        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);
+        SharedPreferences pref =
+                mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
         if (!pref.contains(address)) {
             return feature;
         }
-        if (pref.getBoolean(address, false)) {
-            feature &= ~BTRC_FEAT_ABSOLUTE_VOLUME;
-        }
-        return feature;
+        return feature & ~BTRC_FEAT_ABSOLUTE_VOLUME;
     }
 
     public void resetBlackList(String address) {
-        SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
-                Context.MODE_PRIVATE);
+        SharedPreferences pref =
+                mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST, Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = pref.edit();
         editor.remove(address);
         editor.apply();
@@ -1455,8 +1567,14 @@
      * This is called from A2dpStateMachine to set A2dp audio state.
      */
     public void setA2dpAudioState(int state) {
-        Message msg = mHandler.obtainMessage(MSG_SET_A2DP_AUDIO_STATE, state, 0);
-        mHandler.sendMessage(msg);
+        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 {
@@ -1464,7 +1582,9 @@
         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");
+                if (DEBUG) {
+                    Log.d(TAG, "User unlocked, initializing player lists");
+                }
                 /* initializing media player's list */
                 buildBrowsablePlayerList();
             }
@@ -1475,10 +1595,12 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (DEBUG) Log.d(TAG, "AvrcpServiceBroadcastReceiver-> Action: " + action);
+            if (DEBUG) {
+                Log.d(TAG, "AvrcpServiceBroadcastReceiver-> Action: " + action);
+            }
 
-            if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
-                    || action.equals(Intent.ACTION_PACKAGE_DATA_CLEARED)) {
+            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();
@@ -1487,11 +1609,12 @@
                     }
                 }
 
-            } else if (action.equals(Intent.ACTION_PACKAGE_ADDED)
-                    || action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
+            } 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 (DEBUG) {
+                    Log.d(TAG, "AvrcpServiceBroadcastReceiver-> packageName: " + packageName);
+                }
                 if (packageName != null) {
                     handlePackageModified(packageName, false);
                 }
@@ -1500,7 +1623,9 @@
     }
 
     private void handlePackageModified(String packageName, boolean removed) {
-        if (DEBUG) Log.d(TAG, "packageName: " + packageName + " removed: " + removed);
+        if (DEBUG) {
+            Log.d(TAG, "packageName: " + packageName + " removed: " + removed);
+        }
 
         if (removed) {
             removeMediaPlayerInfo(packageName);
@@ -1520,20 +1645,23 @@
     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);
+        List<ResolveInfo> resInfos =
+                mPackageManager.queryIntentServices(intent, PackageManager.MATCH_ALL);
         for (ResolveInfo resolveInfo : resInfos) {
             if (resolveInfo.serviceInfo.packageName.equals(newPackageName)) {
-                if (DEBUG)
+                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");
+            if (DEBUG) {
+                Log.d(TAG, "isBrowsableListUpdated: browsable list size mismatch, true");
+            }
             return true;
         }
 
@@ -1542,7 +1670,9 @@
     }
 
     private void removePackageFromBrowseList(String packageName) {
-        if (DEBUG) Log.d(TAG, "removePackageFromBrowseList: " + packageName);
+        if (DEBUG) {
+            Log.d(TAG, "removePackageFromBrowseList: " + packageName);
+        }
         synchronized (mBrowsePlayerInfoList) {
             int browseInfoID = getBrowseId(packageName);
             if (browseInfoID != -1) {
@@ -1572,8 +1702,10 @@
             browseInfoID = -1;
         }
 
-        if (DEBUG) Log.d(TAG, "getBrowseId for packageName: " + packageName +
-                " , browseInfoID: " + browseInfoID);
+        if (DEBUG) {
+            Log.d(TAG, "getBrowseId for packageName: " + packageName + " , browseInfoID: "
+                    + browseInfoID);
+        }
         return browseInfoID;
     }
 
@@ -1638,8 +1770,8 @@
                 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);
+                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
@@ -1652,8 +1784,9 @@
             setBrowsedPlayerRspNative(bdaddr, status, (byte) 0x00, 0, null);
         }
 
-        if (DEBUG) Log.d(TAG, "setBrowsedPlayer for selectedId: " + selectedId +
-                " , status: " + status);
+        if (DEBUG) {
+            Log.d(TAG, "setBrowsedPlayer for selectedId: " + selectedId + " , status: " + status);
+        }
     }
 
     private MediaSessionManager.OnActiveSessionsChangedListener mActiveSessionListener =
@@ -1666,16 +1799,21 @@
                     // Update the current players
                     for (android.media.session.MediaController controller : newControllers) {
                         String packageName = controller.getPackageName();
-                        if (DEBUG) Log.v(TAG, "ActiveSession: " + MediaController.wrap(controller));
+                        if (DEBUG) {
+                            Log.v(TAG, "ActiveSession: " + MediaControllerFactory.wrap(controller));
+                        }
                         // Only use the first (highest priority) controller from each package
-                        if (updatedPackages.contains(packageName)) continue;
+                        if (updatedPackages.contains(packageName)) {
+                            continue;
+                        }
                         addMediaPlayerController(controller);
                         updatedPackages.add(packageName);
                     }
 
                     if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) {
-                        if (DEBUG)
+                        if (DEBUG) {
                             Log.v(TAG, "No addressed player but active sessions, taking first.");
+                        }
                         setAddressedMediaSessionPackage(newControllers.get(0).getPackageName());
                     }
                     updateCurrentMediaState();
@@ -1693,8 +1831,12 @@
             return;
         }
         // No change.
-        if (getPackageName(mCurrAddrPlayerID).equals(packageName)) return;
-        if (DEBUG) Log.v(TAG, "Changing addressed media session to " + packageName);
+        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);
@@ -1704,7 +1846,9 @@
             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());
+                    if (DEBUG) {
+                        Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue());
+                    }
                     updateCurrentController(newAddrID, mCurrBrowsePlayerID);
                     updateCurrentMediaState();
                     return;
@@ -1722,7 +1866,9 @@
             Log.d(TAG, "Ignore active media session change to telecom");
             return;
         }
-        if (DEBUG) Log.v(TAG, "Set active media session " + activeController.getPackageName());
+        if (DEBUG) {
+            Log.v(TAG, "Set active media session " + activeController.getPackageName());
+        }
         addMediaPlayerController(activeController);
         setAddressedMediaSessionPackage(activeController.getPackageName());
     }
@@ -1733,15 +1879,17 @@
         /* creating new instance for Browse Media Player */
         String browseService = getBrowseServiceName(packageName);
         if (!browseService.isEmpty()) {
-            mAvrcpBrowseManager.getBrowsedMediaPlayer(bdaddr).setBrowsed(
-                    packageName, browseService);
+            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);
+        if (DEBUG) {
+            Log.d(TAG,
+                    "startBrowseService for packageName: " + packageName + ", status = " + status);
+        }
         return status;
     }
 
@@ -1756,8 +1904,10 @@
             }
         }
 
-        if (DEBUG) Log.d(TAG, "getBrowseServiceName for packageName: " + packageName +
-                ", browseServiceName = " + browseServiceName);
+        if (DEBUG) {
+            Log.d(TAG, "getBrowseServiceName for packageName: " + packageName
+                    + ", browseServiceName = " + browseServiceName);
+        }
         return browseServiceName;
     }
 
@@ -1773,7 +1923,9 @@
                 String serviceName = info.serviceInfo.name;
                 String packageName = info.serviceInfo.packageName;
 
-                if (DEBUG) Log.d(TAG, "Adding " + serviceName + " to list of browsable players");
+                if (DEBUG) {
+                    Log.d(TAG, "Adding " + serviceName + " to list of browsable players");
+                }
                 BrowsePlayerInfo currentPlayer =
                         new BrowsePlayerInfo(packageName, displayableName, serviceName);
                 mBrowsePlayerInfoList.add(currentPlayer);
@@ -1798,14 +1950,17 @@
             mMediaPlayerInfoList.clear();
 
             if (mMediaSessionManager == null) {
-                if (DEBUG) Log.w(TAG, "initMediaPlayersList: no media session manager!");
+                if (DEBUG) {
+                    Log.w(TAG, "initMediaPlayersList: no media session manager!");
+                }
                 return;
             }
 
             List<android.media.session.MediaController> controllers =
                     mMediaSessionManager.getActiveSessions(null);
-            if (DEBUG)
+            if (DEBUG) {
                 Log.v(TAG, "initMediaPlayerInfoList: " + controllers.size() + " controllers");
+            }
             /* Initializing all media players */
             for (android.media.session.MediaController controller : controllers) {
                 addMediaPlayerController(controller);
@@ -1845,7 +2000,7 @@
     /** 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(MediaController.wrap(controller),
+        MediaPlayerInfo info = new MediaPlayerInfo(MediaControllerFactory.wrap(controller),
                 AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
                 getBluetoothPlayState(controller.getPlaybackState()),
                 getFeatureBitMask(packageName), controller.getPackageName(),
@@ -1889,7 +2044,9 @@
             }
             mMediaPlayerInfoList.put(updateId, info);
         }
-        if (DEBUG) Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString());
+        if (DEBUG) {
+            Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString());
+        }
         if (currentRemoved || updateId == mCurrAddrPlayerID) {
             updateCurrentController(updateId, mCurrBrowsePlayerID);
         }
@@ -1907,8 +2064,9 @@
                 }
             }
             if (removeKey != -1) {
-                if (DEBUG)
+                if (DEBUG) {
                     Log.d(TAG, "remove #" + removeKey + ":" + mMediaPlayerInfoList.get(removeKey));
+                }
                 mAvailablePlayerViewChanged = true;
                 return mMediaPlayerInfoList.remove(removeKey);
             }
@@ -1919,7 +2077,9 @@
 
     /** 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;
+        if (controller == null) {
+            return;
+        }
         synchronized (mMediaPlayerInfoList) {
             for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
                 MediaPlayerInfo info = entry.getValue();
@@ -2019,13 +2179,17 @@
             /* 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");
+                    if (DEBUG) {
+                        Log.v(TAG, "isBrowseSupported for " + packageName + ": true");
+                    }
                     return true;
                 }
             }
         }
 
-        if (DEBUG) Log.v(TAG, "isBrowseSupported for " + packageName + ": false");
+        if (DEBUG) {
+            Log.v(TAG, "isBrowseSupported for " + packageName + ": false");
+        }
         return false;
     }
 
@@ -2041,7 +2205,9 @@
         }
 
         String packageName = player.getPackageName();
-        if (DEBUG) Log.v(TAG, "Player " + id + " package: " + packageName);
+        if (DEBUG) {
+            Log.v(TAG, "Player " + id + " package: " + packageName);
+        }
         return packageName;
     }
 
@@ -2051,10 +2217,12 @@
 
         Map<String, BrowsedMediaPlayer> connList = mAvrcpBrowseManager.getConnList();
         String bdaddrStr = new String(bdaddr);
-        if(connList.containsKey(bdaddrStr)){
+        if (connList.containsKey(bdaddrStr)) {
             browsedPlayerPackage = connList.get(bdaddrStr).getPackageName();
         }
-        if (DEBUG) Log.v(TAG, "getCurrentBrowsedPlayerPackage: " + browsedPlayerPackage);
+        if (DEBUG) {
+            Log.v(TAG, "getCurrentBrowsedPlayerPackage: " + browsedPlayerPackage);
+        }
         return browsedPlayerPackage;
     }
 
@@ -2072,17 +2240,23 @@
     private MediaPlayerInfo getMediaPlayerInfo(String packageName) {
         synchronized (mMediaPlayerInfoList) {
             if (mMediaPlayerInfoList.isEmpty()) {
-                if (DEBUG) Log.v(TAG, "getMediaPlayerInfo: Media players list empty");
+                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);
+                    if (DEBUG) {
+                        Log.v(TAG, "getMediaPlayerInfo: Found " + packageName);
+                    }
                     return info;
                 }
             }
-            if (DEBUG) Log.w(TAG, "getMediaPlayerInfo: " + packageName + " not found");
+            if (DEBUG) {
+                Log.w(TAG, "getMediaPlayerInfo: " + packageName + " not found");
+            }
             return null;
         }
     }
@@ -2110,10 +2284,11 @@
             int players = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0;
             for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) {
                 int idx = players;
-                if (entry.getKey() == mCurrAddrPlayerID)
+                if (entry.getKey() == mCurrAddrPlayerID) {
                     idx = 0;
-                else
+                } else {
                     continue; // TODO(apanicke): Remove, see above note
+                }
                 MediaPlayerInfo info = entry.getValue();
                 playerIds[idx] = entry.getKey();
                 playerTypes[idx] = info.getMajorType();
@@ -2134,14 +2309,18 @@
                 /* printLogs */
                 if (DEBUG) {
                     Log.d(TAG, "Player " + playerIds[idx] + ": " + displayableNameArray[idx]
-                                    + " type: " + playerTypes[idx] + ", " + playerSubTypes[idx]
-                                    + " status: " + playStatusValues[idx]);
+                            + " type: " + playerTypes[idx] + ", " + playerSubTypes[idx]
+                            + " status: " + playStatusValues[idx]);
                 }
 
-                if (idx != 0) players++;
+                if (idx != 0) {
+                    players++;
+                }
             }
 
-            if (DEBUG) Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
+            if (DEBUG) {
+                Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers);
+            }
 
             return new MediaPlayerListRsp(AvrcpConstants.RSP_NO_ERROR, sUIDCounter, numPlayers,
                     AvrcpConstants.BTRC_ITEM_PLAYER, playerIds, playerTypes, playerSubTypes,
@@ -2149,7 +2328,7 @@
         }
     }
 
-     /* build media player list and send it to remote. */
+    /* build media player list and send it to remote. */
     private void handleMediaPlayerListRsp(AvrcpCmd.FolderItemsCmd folderObj) {
         MediaPlayerListRsp rspObj = null;
         synchronized (mMediaPlayerInfoList) {
@@ -2161,16 +2340,18 @@
             }
             if (folderObj.mStartItem >= numPlayers) {
                 Log.i(TAG, "handleMediaPlayerListRsp: start = " + folderObj.mStartItem
-                                + " > num of items = " + numPlayers);
+                        + " > 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");
+        if (DEBUG) {
+            Log.d(TAG, "handleMediaPlayerListRsp: sending " + rspObj.mNumItems + " players");
+        }
         mediaPlayerListRspNative(folderObj.mAddress, rspObj.mStatus, rspObj.mUIDCounter,
-                rspObj.itemType, rspObj.mNumItems, rspObj.mPlayerIds, rspObj.mPlayerTypes,
+                rspObj.mItemType, rspObj.mNumItems, rspObj.mPlayerIds, rspObj.mPlayerTypes,
                 rspObj.mPlayerSubTypes, rspObj.mPlayStatusValues, rspObj.mFeatureBitMaskValues,
                 rspObj.mPlayerNameList);
     }
@@ -2183,10 +2364,13 @@
 
         MediaController newController = null;
         MediaPlayerInfo info = getAddressedPlayerInfo();
-        if (info != null) newController = info.getMediaController();
+        if (info != null) {
+            newController = info.getMediaController();
+        }
 
-        if (DEBUG)
+        if (DEBUG) {
             Log.d(TAG, "updateCurrentController: " + mMediaController + " to " + newController);
+        }
         synchronized (this) {
             if (mMediaController == null || (!mMediaController.equals(newController))) {
                 if (mMediaController != null) {
@@ -2205,14 +2389,15 @@
     }
 
     /* Handle getfolderitems for scope = VFS, Search, NowPlayingList */
-    private void handleGetFolderItemBrowseResponse(AvrcpCmd.FolderItemsCmd folderObj, byte[] bdaddr) {
+    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));
+                        + Utils.getAddressStringFromByte(bdaddr));
                 getFolderItemsRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR, (short) 0,
                         (byte) 0x00, 0, null, null, null, null, null, null, null, null);
                 return;
@@ -2233,9 +2418,11 @@
 
     /* 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);
+        if (DEBUG) {
+            Log.v(TAG,
+                    "updateNewIds: Addressed:" + mCurrAddrPlayerID + " to " + addrId + ", Browse:"
+                            + mCurrBrowsePlayerID + " to " + browseId);
+        }
         mCurrAddrPlayerID = addrId;
         mCurrBrowsePlayerID = browseId;
     }
@@ -2249,26 +2436,25 @@
             e.printStackTrace();
         }
 
-        return (String) (appInfo != null ? mPackageManager
-                .getApplicationLabel(appInfo) : "Unknown");
+        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");
+        } 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");
+                Log.e(TAG, "handlePlayItemResponse: Remote requested playitem "
+                        + "before setbrowsedplayer");
                 playItemRspNative(bdaddr, AvrcpConstants.RSP_INTERNAL_ERR);
             }
         }
@@ -2277,14 +2463,14 @@
     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);
+            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);
+                getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_NO_AVBL_PLAY, (byte) 0,
+                        null, null);
                 return;
             }
             mAddressedMediaPlayer.getItemAttr(itemAttr.mAddress, itemAttr, mMediaController);
@@ -2295,8 +2481,8 @@
             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);
+            getItemAttrRspNative(itemAttr.mAddress, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0, null,
+                    null);
         }
     }
 
@@ -2307,7 +2493,9 @@
             synchronized (mMediaPlayerInfoList) {
                 numPlayers = mMediaPlayerInfoList.size();
             }
-            if (DEBUG) Log.d(TAG, "handleGetTotalNumOfItemsResponse: " + numPlayers + " players.");
+            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);
@@ -2335,7 +2523,9 @@
         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);
+            if (DEBUG) {
+                Log.d(TAG, browsedPlayer + " is not addressed player " + packageName);
+            }
             return false;
         }
         return true;
@@ -2344,8 +2534,10 @@
     /* 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);
+        if (DEBUG) {
+            Log.d(TAG, "isPackageNameValid: browsedPackage = " + browsedPackage + "isValid = "
+                    + isValid);
+        }
         return isValid;
     }
 
@@ -2353,7 +2545,9 @@
     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);
+        if (DEBUG) {
+            Log.d(TAG, "isPlayerAlreadyAddressed: isAddressed = " + isAddressed);
+        }
         return isAddressed;
     }
 
@@ -2374,23 +2568,24 @@
         ProfileService.println(sb, "mLastDirection: " + mLastDirection);
         ProfileService.println(sb, "mVolumeStep: " + mVolumeStep);
         ProfileService.println(sb, "mAudioStreamMax: " + mAudioStreamMax);
-        ProfileService.println(sb, "mVolCmdAdjustInProgress: " + mVolCmdAdjustInProgress);
         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 "
+            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,
+                        ((mCurrAddrPlayerID == key) ? " *#" : "  #") + entry.getKey() + ": " + entry
+                                .getValue());
             }
         }
 
@@ -2399,8 +2594,9 @@
 
         ProfileService.println(sb, "");
         ProfileService.println(sb, mPassthroughDispatched + " passthrough operations: ");
-        if (mPassthroughDispatched > mPassthroughLogs.size())
+        if (mPassthroughDispatched > mPassthroughLogs.size()) {
             ProfileService.println(sb, "  (last " + mPassthroughLogs.size() + ")");
+        }
         synchronized (mPassthroughLogs) {
             for (MediaKeyLog log : mPassthroughLogs) {
                 ProfileService.println(sb, "  " + log);
@@ -2421,14 +2617,20 @@
         if (allKeys.isEmpty()) {
             ProfileService.println(sb, "  None");
         } else {
-            for (String key : allKeys.keySet()) {
-                ProfileService.println(sb, "  " + key);
+            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 {
-        Map<String, BrowsedMediaPlayer> connList = new HashMap<String, BrowsedMediaPlayer>();
+        public Map<String, BrowsedMediaPlayer> connList = new HashMap<String, BrowsedMediaPlayer>();
         private AvrcpMediaRspInterface mMediaInterface;
         private Context mContext;
 
@@ -2484,8 +2686,8 @@
             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));
+                data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
+                        s.charAt(i + 1), 16));
             }
             return data;
         }
@@ -2498,12 +2700,14 @@
     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)) {
@@ -2511,60 +2715,73 @@
             }
         }
 
+        @Override
         public void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj) {
             if (rspObj != null && rspStatus == AvrcpConstants.RSP_NO_ERROR) {
-                if (!mediaPlayerListRspNative(address, rspStatus, sUIDCounter, rspObj.itemType,
-                            rspObj.mNumItems, rspObj.mPlayerIds, rspObj.mPlayerTypes,
-                            rspObj.mPlayerSubTypes, rspObj.mPlayStatusValues,
-                            rspObj.mFeatureBitMaskValues, rspObj.mPlayerNameList))
+                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))
+                        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))
+                        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))
+                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))
+            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))
+                        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))
+                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)) {
@@ -2572,27 +2789,33 @@
             }
         }
 
+        @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.");
+                if (DEBUG) {
+                    Log.d(TAG, "NowPlayingListChanged: Not registered or requesting.");
+                }
                 return;
             }
 
@@ -2602,6 +2825,7 @@
             mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED;
         }
 
+        @Override
         public void trackChangedRsp(int type, byte[] uid) {
             if (!registerNotificationRspTrackChangeNative(type, uid)) {
                 Log.e(TAG, "registerNotificationRspTrackChangeNative failed!");
@@ -2623,7 +2847,9 @@
             return;
         }
         int action = KeyEvent.ACTION_DOWN;
-        if (state == AvrcpConstants.KEY_STATE_RELEASE) action = KeyEvent.ACTION_UP;
+        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);
@@ -2804,71 +3030,94 @@
     // Do not modify without updating the HAL bt_rc.h files.
 
     // match up with btrc_play_status_t enum of bt_rc.h
-    final static byte PLAYSTATUS_STOPPED = 0;
-    final static byte PLAYSTATUS_PLAYING = 1;
-    final static byte PLAYSTATUS_PAUSED = 2;
-    final static byte PLAYSTATUS_FWD_SEEK = 3;
-    final static byte PLAYSTATUS_REV_SEEK = 4;
-    final static byte PLAYSTATUS_ERROR = (byte) 255;
+    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
-    final static int MEDIA_ATTR_TITLE = 1;
-    final static int MEDIA_ATTR_ARTIST = 2;
-    final static int MEDIA_ATTR_ALBUM = 3;
-    final static int MEDIA_ATTR_TRACK_NUM = 4;
-    final static int MEDIA_ATTR_NUM_TRACKS = 5;
-    final static int MEDIA_ATTR_GENRE = 6;
-    final static int MEDIA_ATTR_PLAYING_TIME = 7;
+    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
-    final static int EVT_PLAY_STATUS_CHANGED = 1;
-    final static int EVT_TRACK_CHANGED = 2;
-    final static int EVT_TRACK_REACHED_END = 3;
-    final static int EVT_TRACK_REACHED_START = 4;
-    final static int EVT_PLAY_POS_CHANGED = 5;
-    final static int EVT_BATT_STATUS_CHANGED = 6;
-    final static int EVT_SYSTEM_STATUS_CHANGED = 7;
-    final static int EVT_APP_SETTINGS_CHANGED = 8;
-    final static int EVENT_NOW_PLAYING_CONTENT_CHANGED = 9;
-    final static int EVT_AVBL_PLAYERS_CHANGED = 0xa;
-    final static int EVT_ADDR_PLAYER_CHANGED = 0xb;
-    final static int EVENT_UIDS_CHANGED = 0x0c;
+    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 native static void classInitNative();
+    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 item_type, int numItems, int[] playerIds, byte[] playerTypes, int[] playerSubTypes,
+            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,
+            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 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
index a0a6441..50a2eeb 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpConstants.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpConstants.java
@@ -29,74 +29,90 @@
 
     /* 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
+    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_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_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 */
+    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_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;
+    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;
+    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;
+    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_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;
+    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};
+    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;
@@ -141,8 +157,8 @@
     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 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
index b0a4c5d..ea6d7b8 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpHelperClasses.java
@@ -18,13 +18,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.media.session.MediaSession;
 
 import com.android.bluetooth.Utils;
 
-import java.util.List;
-import java.util.Arrays;
 import java.util.ArrayDeque;
+import java.util.Arrays;
 import java.util.Collection;
 
 /*************************************************************************************************
@@ -35,7 +33,7 @@
 
 class AvrcpCmd {
 
-    public AvrcpCmd() {}
+    AvrcpCmd() {}
 
     /* Helper classes to pass parameters from callbacks to Avrcp handler */
     class FolderItemsCmd {
@@ -46,8 +44,8 @@
         int[] mAttrIDs;
         public byte[] mAddress;
 
-        public FolderItemsCmd(byte[] address, byte scope, long startItem, long endItem,
-                byte numAttr, int[] attrIds) {
+        FolderItemsCmd(byte[] address, byte scope, long startItem, long endItem, byte numAttr,
+                int[] attrIds) {
             mAddress = address;
             this.mScope = scope;
             this.mStartItem = startItem;
@@ -56,6 +54,7 @@
             this.mAttrIDs = attrIds;
         }
 
+        @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("[FolderItemCmd: scope " + mScope);
@@ -78,7 +77,7 @@
         int[] mAttrIDs;
         public byte[] mAddress;
 
-        public ItemAttrCmd(byte[] address, byte scope, byte[] uid, int uidCounter, byte numAttr,
+        ItemAttrCmd(byte[] address, byte scope, byte[] uid, int uidCounter, byte numAttr,
                 int[] attrIDs) {
             mAddress = address;
             mScope = scope;
@@ -88,6 +87,7 @@
             mAttrIDs = attrIDs;
         }
 
+        @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("[ItemAttrCmd: scope " + mScope);
@@ -106,7 +106,7 @@
         int[] mAttrIDs;
         public byte[] mAddress;
 
-        public ElementAttrCmd(byte[] address, byte numAttr, int[] attrIDs) {
+        ElementAttrCmd(byte[] address, byte numAttr, int[] attrIDs) {
             mAddress = address;
             mNumAttr = numAttr;
             mAttrIDs = attrIDs;
@@ -118,7 +118,7 @@
 class MediaPlayerListRsp {
     byte mStatus;
     short mUIDCounter;
-    byte itemType;
+    byte mItemType;
     int[] mPlayerIds;
     byte[] mPlayerTypes;
     int[] mPlayerSubTypes;
@@ -127,13 +127,13 @@
     String[] mPlayerNameList;
     int mNumItems;
 
-    public MediaPlayerListRsp(byte status, short UIDCounter, int numItems, byte itemType,
-            int[] playerIds, byte[] playerTypes, int[] playerSubTypes, byte[] playStatusValues,
+    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.mUIDCounter = uidCounter;
         this.mNumItems = numItems;
-        this.itemType = itemType;
+        this.mItemType = itemType;
         this.mPlayerIds = playerIds;
         this.mPlayerTypes = playerTypes;
         this.mPlayerSubTypes = new int[numItems];
@@ -163,20 +163,20 @@
     int[] mAttrIds;
     String[] mAttrValues;
 
-    public 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;
+    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.mItemTypes = itemTypes;
+        this.mItemUid = itemsUid;
         this.mDisplayNames = displayNameArray;
-        this.mAttributesNum = AttributesNum;
-        this.mAttrIds = AttrIds;
+        this.mAttributesNum = attributesNum;
+        this.mAttrIds = attrIds;
         this.mAttrValues = attrValues;
     }
 }
@@ -187,7 +187,7 @@
     int[] mAttributesIds;
     String[] mAttributesArray;
 
-    public ItemAttrRsp(byte status, int[] attributesIds, String[] attributesArray) {
+    ItemAttrRsp(byte status, int[] attributesIds, String[] attributesArray) {
         mStatus = status;
         mNumAttr = (byte) attributesIds.length;
         mAttributesIds = attributesIds;
@@ -198,23 +198,23 @@
 /* stores information of Media Players in the system */
 class MediaPlayerInfo {
 
-    private byte majorType;
-    private int subType;
-    private byte playStatus;
-    private short[] featureBitMask;
-    private @NonNull String packageName;
-    private @NonNull String displayableName;
-    private @Nullable MediaController mediaController;
+    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.playStatus = playStatus;
+        this.mPlayStatus = playStatus;
         // store a copy the FeatureBitMask array
-        this.featureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
-        Arrays.sort(this.featureBitMask);
+        this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
+        Arrays.sort(this.mFeatureBitMask);
         this.setPackageName(packageName);
         this.setDisplayableName(displayableName);
         this.setMediaController(controller);
@@ -222,80 +222,88 @@
 
     /* getters and setters */
     byte getPlayStatus() {
-        return playStatus;
+        return mPlayStatus;
     }
 
     void setPlayStatus(byte playStatus) {
-        this.playStatus = playStatus;
+        this.mPlayStatus = playStatus;
     }
 
     MediaController getMediaController() {
-        return mediaController;
+        return mMediaController;
     }
 
     void setMediaController(MediaController mediaController) {
         if (mediaController != null) {
-            this.packageName = mediaController.getPackageName();
+            this.mPackageName = mediaController.getPackageName();
         }
-        this.mediaController = mediaController;
+        this.mMediaController = mediaController;
     }
 
     void setPackageName(@NonNull String name) {
         // Controller determines package name when it is set.
-        if (mediaController != null) return;
-        this.packageName = name;
+        if (mMediaController != null) {
+            return;
+        }
+        this.mPackageName = name;
     }
 
     String getPackageName() {
-        if (mediaController != null) {
-            return mediaController.getPackageName();
-        } else if (packageName != null) {
-            return packageName;
+        if (mMediaController != null) {
+            return mMediaController.getPackageName();
+        } else if (mPackageName != null) {
+            return mPackageName;
         }
         return null;
     }
 
     byte getMajorType() {
-        return majorType;
+        return mMajorType;
     }
 
     void setMajorType(byte majorType) {
-        this.majorType = majorType;
+        this.mMajorType = majorType;
     }
 
     int getSubType() {
-        return subType;
+        return mSubType;
     }
 
     void setSubType(int subType) {
-        this.subType = subType;
+        this.mSubType = subType;
     }
 
     String getDisplayableName() {
-        return displayableName;
+        return mDisplayableName;
     }
 
     void setDisplayableName(@Nullable String displayableName) {
-        if (displayableName == null) displayableName = "";
-        this.displayableName = displayableName;
+        if (displayableName == null) {
+            displayableName = "";
+        }
+        this.mDisplayableName = displayableName;
     }
 
     short[] getFeatureBitMask() {
-        return featureBitMask;
+        return mFeatureBitMask;
     }
 
     void setFeatureBitMask(short[] featureBitMask) {
         synchronized (this) {
-            this.featureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
-            Arrays.sort(this.featureBitMask);
+            this.mFeatureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length);
+            Arrays.sort(this.mFeatureBitMask);
         }
     }
 
     boolean isBrowseSupported() {
         synchronized (this) {
-            if (this.featureBitMask == null) return false;
-            for (short bit : this.featureBitMask) {
-                if (bit == AvrcpConstants.AVRC_PF_BROWSE_BIT_NO) return true;
+            if (this.mFeatureBitMask == null) {
+                return false;
+            }
+            for (short bit : this.mFeatureBitMask) {
+                if (bit == AvrcpConstants.AVRC_PF_BROWSE_BIT_NO) {
+                    return true;
+                }
             }
         }
         return false;
@@ -304,9 +312,9 @@
     /** 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.majorType == other.getMajorType()) && (this.subType == other.getSubType())
-                && Arrays.equals(this.featureBitMask, other.getFeatureBitMask())
-                && this.displayableName.equals(other.getDisplayableName());
+        return (this.mMajorType == other.getMajorType()) && (this.mSubType == other.getSubType())
+                && Arrays.equals(this.mFeatureBitMask, other.getFeatureBitMask())
+                && this.mDisplayableName.equals(other.getDisplayableName());
     }
 
     @Override
@@ -317,11 +325,13 @@
         sb.append(" (as '" + getDisplayableName() + "')");
         sb.append(" Type = " + getMajorType());
         sb.append(", SubType = " + getSubType());
-        sb.append(", Status = " + playStatus);
+        sb.append(", Status = " + mPlayStatus);
         sb.append(" Feature Bits [");
         short[] bits = getFeatureBitMask();
         for (int i = 0; i < bits.length; i++) {
-            if (i != 0) sb.append(" ");
+            if (i != 0) {
+                sb.append(" ");
+            }
             sb.append(bits[i]);
         }
         sb.append("] Controller: ");
@@ -332,11 +342,11 @@
 
 /* stores information for browsable Media Players available in the system */
 class BrowsePlayerInfo {
-    String packageName;
-    String displayableName;
-    String serviceClass;
+    public String packageName;
+    public String displayableName;
+    public String serviceClass;
 
-    public BrowsePlayerInfo(String packageName, String displayableName, String serviceClass) {
+    BrowsePlayerInfo(String packageName, String displayableName, String serviceClass) {
         this.packageName = packageName;
         this.displayableName = displayableName;
         this.serviceClass = serviceClass;
@@ -354,8 +364,7 @@
 }
 
 class FolderItemsData {
-    /* initialize sizes for rsp parameters */
-    int mNumItems;
+    /* initialize sizes for rsp parameters */ int mNumItems;
     int[] mAttributesNum;
     byte[] mFolderTypes;
     byte[] mItemTypes;
@@ -364,9 +373,9 @@
     String[] mDisplayNames;
     int[] mAttrIds;
     String[] mAttrValues;
-    int attrCounter;
+    int mAttrCounter;
 
-    public FolderItemsData(int size) {
+    FolderItemsData(int size) {
         mNumItems = size;
         mAttributesNum = new int[size];
 
@@ -393,24 +402,26 @@
 class EvictingQueue<E> extends ArrayDeque<E> {
     private int mMaxSize;
 
-    public EvictingQueue(int maxSize) {
+    EvictingQueue(int maxSize) {
         super();
         mMaxSize = maxSize;
     }
 
-    public EvictingQueue(int maxSize, int initialElements) {
+    EvictingQueue(int maxSize, int initialElements) {
         super(initialElements);
         mMaxSize = maxSize;
     }
 
-    public EvictingQueue(int maxSize, Collection<? extends E> c) {
+    EvictingQueue(int maxSize, Collection<? extends E> c) {
         super(c);
         mMaxSize = maxSize;
     }
 
     @Override
     public void addFirst(E e) {
-        if (super.size() == mMaxSize) return;
+        if (super.size() == mMaxSize) {
+            return;
+        }
         super.addFirst(e);
     }
 
@@ -424,7 +435,9 @@
 
     @Override
     public boolean offerFirst(E e) {
-        if (super.size() == mMaxSize) return false;
+        if (super.size() == mMaxSize) {
+            return false;
+        }
         return super.offerFirst(e);
     }
 
diff --git a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
index 1c7b87c..8cab68e 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpMediaRspInterface.java
@@ -23,32 +23,31 @@
  ************************************************************************************************/
 
 public interface AvrcpMediaRspInterface {
-    public void setAddrPlayerRsp(byte[] address, int rspStatus);
+    void setAddrPlayerRsp(byte[] address, int rspStatus);
 
-    public void setBrowsedPlayerRsp(byte[] address, int rspStatus, byte depth, int numItems,
-        String[] textArray);
+    void setBrowsedPlayerRsp(byte[] address, int rspStatus, byte depth, int numItems,
+            String[] textArray);
 
-    public void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj);
+    void mediaPlayerListRsp(byte[] address, int rspStatus, MediaPlayerListRsp rspObj);
 
-    public void folderItemsRsp(byte[] address, int rspStatus, FolderItemsRsp rspObj);
+    void folderItemsRsp(byte[] address, int rspStatus, FolderItemsRsp rspObj);
 
-    public void changePathRsp(byte[] address, int rspStatus, int numItems);
+    void changePathRsp(byte[] address, int rspStatus, int numItems);
 
-    public void getItemAttrRsp(byte[] address, int rspStatus, ItemAttrRsp rspObj);
+    void getItemAttrRsp(byte[] address, int rspStatus, ItemAttrRsp rspObj);
 
-    public void playItemRsp(byte[] address, int rspStatus);
+    void playItemRsp(byte[] address, int rspStatus);
 
-    public void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter,
-        int numItems);
+    void getTotalNumOfItemsRsp(byte[] address, int rspStatus, int uidCounter, int numItems);
 
-    public void addrPlayerChangedRsp(int type, int playerId, int uidCounter);
+    void addrPlayerChangedRsp(int type, int playerId, int uidCounter);
 
-    public void avalPlayerChangedRsp(byte[] address, int type);
+    void avalPlayerChangedRsp(byte[] address, int type);
 
-    public void uidsChangedRsp(int type);
+    void uidsChangedRsp(int type);
 
-    public void nowPlayingChangedRsp(int type);
+    void nowPlayingChangedRsp(int type);
 
-    public void trackChangedRsp(int type, byte[] uid);
+    void trackChangedRsp(int type, byte[] uid);
 }
 
diff --git a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
index 397e17a..0889d90 100644
--- a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
+++ b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java
@@ -24,7 +24,6 @@
 import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowser.MediaItem;
 import android.media.session.MediaSession;
-import android.media.session.MediaSession.QueueItem;
 import android.os.Bundle;
 import android.util.Log;
 
@@ -88,7 +87,7 @@
         private String mCallbackPackageName;
         private MediaBrowser mBrowser;
 
-        public MediaConnectionCallback(String packageName) {
+        MediaConnectionCallback(String packageName) {
             this.mCallbackPackageName = packageName;
         }
 
@@ -99,7 +98,9 @@
         @Override
         public void onConnected() {
             mConnState = CONNECTED;
-            if (DEBUG) Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName);
+            if (DEBUG) {
+                Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName);
+            }
             /* perform init tasks and set player as browsed player on successful connection */
             onBrowseConnect(mCallbackPackageName, mBrowser);
 
@@ -115,7 +116,7 @@
             Log.e(TAG, "mediaBrowser Connection failed with " + mPackageName
                     + ", Sending fail response!");
             mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
-                (byte)0x00, 0, null);
+                    (byte) 0x00, 0, null);
         }
 
         @Override
@@ -127,47 +128,51 @@
     }
 
     /* Subscription callback handler. Subscribe to a folder to get its contents */
-    private MediaBrowser.SubscriptionCallback folderItemsCb =
+    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());
+                @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);
-        }
+                    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);
-        }
-    };
+                /* 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;
 
-        public ItemAttribSubscriber(
-                @NonNull AvrcpCmd.ItemAttrCmd attrReq, @NonNull String mediaId) {
+        ItemAttribSubscriber(@NonNull AvrcpCmd.ItemAttrCmd attrReq, @NonNull String mediaId) {
             mAttrReq = attrReq;
             mMediaId = mediaId;
         }
@@ -175,7 +180,9 @@
         @Override
         public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
             String logprefix = "ItemAttribSubscriber(" + mMediaId + "): ";
-            if (DEBUG) Log.d(TAG, logprefix + "OnChildren Loaded");
+            if (DEBUG) {
+                Log.d(TAG, logprefix + "OnChildren Loaded");
+            }
             int status = AvrcpConstants.RSP_INV_ITEM;
 
             if (children == null) {
@@ -184,7 +191,9 @@
                 /* find the item in the folder */
                 for (MediaBrowser.MediaItem item : children) {
                     if (item.getMediaId().equals(mMediaId)) {
-                        if (DEBUG) Log.d(TAG, logprefix + "found item");
+                        if (DEBUG) {
+                            Log.d(TAG, logprefix + "found item");
+                        }
                         getItemAttrFilterAttr(item);
                         status = AvrcpConstants.RSP_NO_ERROR;
                         break;
@@ -248,7 +257,9 @@
 
             /* 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);
+            for (int i = 0; i < attrIdArray.size(); i++) {
+                attrIds[i] = attrIdArray.get(i);
+            }
 
             attrValues = attrValueArray.toArray(new String[attrIdArray.size()]);
 
@@ -259,7 +270,7 @@
     }
 
     /* Constructor */
-    public BrowsedMediaPlayer(byte[] address, Context context,
+    BrowsedMediaPlayer(byte[] address, Context context,
             AvrcpMediaRspInterface mAvrcpMediaRspInterface) {
         mContext = context;
         mMediaInterface = mAvrcpMediaRspInterface;
@@ -270,15 +281,15 @@
     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);
+                    + 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);
+            mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR,
+                    (byte) 0x00, 0, null);
             return;
         }
 
@@ -290,7 +301,9 @@
                 Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "no Session token");
             } else {
                 /* update to the new MediaBrowser */
-                if (mMediaBrowser != null) mMediaBrowser.disconnect();
+                if (mMediaBrowser != null) {
+                    mMediaBrowser.disconnect();
+                }
                 mMediaBrowser = browser;
                 mPackageName = connectedPackage;
 
@@ -305,10 +318,9 @@
                     mPathStack.push(mMediaId);
                 }
 
-                mMediaController = MediaController.wrap(
-                    new android.media.session.MediaController(mContext, token));
+                mMediaController = MediaControllerFactory.make(mContext, token);
                 /* get root folder items */
-                mMediaBrowser.subscribe(mRootFolderUid, folderItemsCb);
+                mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
                 return;
             }
         } catch (NullPointerException ex) {
@@ -316,8 +328,8 @@
             ex.printStackTrace();
         }
 
-        mMediaInterface.setBrowsedPlayerRsp(
-                mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00, 0, null);
+        mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00,
+                0, null);
     }
 
     public void setBrowsed(String packageName, String cls) {
@@ -335,8 +347,9 @@
 
         /* Bind to MediaBrowseService of MediaPlayer */
         MediaConnectionCallback callback = new MediaConnectionCallback(packageName);
-        MediaBrowser tempBrowser = new MediaBrowser(
-                mContext, new ComponentName(packageName, mClassName), callback, null);
+        MediaBrowser tempBrowser =
+                new MediaBrowser(mContext, new ComponentName(packageName, mClassName), callback,
+                        null);
         callback.setBrowser(tempBrowser);
 
         tempBrowser.connect();
@@ -344,7 +357,9 @@
 
     /* called when connection to media player is closed */
     public void cleanup() {
-        if (DEBUG) Log.d(TAG, "cleanup");
+        if (DEBUG) {
+            Log.d(TAG, "cleanup");
+        }
 
         if (mConnState != DISCONNECTED) {
             mMediaBrowser.disconnect();
@@ -358,7 +373,9 @@
 
     public boolean isPlayerConnected() {
         if (mMediaBrowser == null) {
-            if (DEBUG) Log.d(TAG, "isPlayerConnected: mMediaBrowser = null!");
+            if (DEBUG) {
+                Log.d(TAG, "isPlayerConnected: mMediaBrowser = null!");
+            }
             return false;
         }
 
@@ -367,10 +384,12 @@
 
     /* returns number of items in new path as reponse */
     public void changePath(byte[] folderUid, byte direction) {
-        if (DEBUG) Log.d(TAG, "changePath.direction = " + direction);
+        if (DEBUG) {
+            Log.d(TAG, "changePath.direction = " + direction);
+        }
         String newPath = "";
 
-        if (isPlayerConnected() == false) {
+        if (!isPlayerConnected()) {
             Log.w(TAG, "changePath: disconnected from player service, sending internal error");
             mMediaInterface.changePathRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, 0);
             return;
@@ -387,21 +406,21 @@
             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) == false) {
+            } 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) == true) {
+            } 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, folderItemsCb);
+                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() == false) {
+            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 */
@@ -411,7 +430,7 @@
                 /* move folder up */
                 mPathStack.pop();
                 newPath = mPathStack.peek();
-                mMediaBrowser.subscribe(newPath, folderItemsCb);
+                mMediaBrowser.subscribe(newPath, mFolderItemsCb);
             }
         } else { /* invalid direction */
             Log.w(TAG, "changePath : Invalid direction " + direction);
@@ -421,7 +440,9 @@
 
     public void getItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) {
         String mediaID;
-        if (DEBUG) Log.d(TAG, "getItemAttr");
+        if (DEBUG) {
+            Log.d(TAG, "getItemAttr");
+        }
 
         /* check if uid is valid by doing a lookup in hashmap */
         mediaID = byteToString(itemAttr.mUid);
@@ -449,7 +470,9 @@
     }
 
     public void getTotalNumOfItems(byte scope) {
-        if (DEBUG) Log.d(TAG, "getTotalNumOfItems scope = " + 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);
@@ -464,8 +487,8 @@
         }
 
         /* find num items using size of already cached folder items */
-        mMediaInterface.getTotalNumOfItemsRsp(
-                mBDAddr, AvrcpConstants.RSP_NO_ERROR, 0, mFolderItems.size());
+        mMediaInterface.getTotalNumOfItemsRsp(mBDAddr, AvrcpConstants.RSP_NO_ERROR, 0,
+                mFolderItems.size());
     }
 
     public void getFolderItemsVFS(AvrcpCmd.FolderItemsCmd reqObj) {
@@ -476,7 +499,9 @@
             return;
         }
 
-        if (DEBUG) Log.d(TAG, "getFolderItemsVFS");
+        if (DEBUG) {
+            Log.d(TAG, "getFolderItemsVFS");
+        }
         mFolderItemsReqObj = reqObj;
 
         if (mFolderItems == null) {
@@ -507,7 +532,9 @@
             if (mMediaController != null) {
                 MediaController.TransportControls mediaControllerCntrl =
                         mMediaController.getTransportControls();
-                if (DEBUG) Log.d(TAG, "Sending playID: " + folderUid);
+                if (DEBUG) {
+                    Log.d(TAG, "Sending playID: " + folderUid);
+                }
 
                 if (scope == AvrcpConstants.BTRC_SCOPE_FILE_SYSTEM) {
                     mediaControllerCntrl.playFromMediaId(folderUid, null);
@@ -530,10 +557,14 @@
      * 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;
+    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);
@@ -543,8 +574,8 @@
             }
             return childrenSubList;
         } catch (IndexOutOfBoundsException ex) {
-            Log.w(TAG, "Index out of bounds start item ="+ startItem + " end item = "+
-                    Math.min(children.size(), endItem + 1));
+            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");
@@ -558,11 +589,12 @@
      */
     public void getFolderItemsFilterAttr(byte[] bdaddr, AvrcpCmd.FolderItemsCmd mFolderItemsReqObj,
             List<MediaBrowser.MediaItem> children, byte scope, long startItem, long endItem) {
-        if (DEBUG)
+        if (DEBUG) {
             Log.d(TAG,
                     "getFolderItemsFilterAttr: startItem =" + startItem + ", endItem = " + endItem);
+        }
 
-        List<MediaBrowser.MediaItem> result_items = new ArrayList<MediaBrowser.MediaItem>();
+        List<MediaBrowser.MediaItem> resultItems = new ArrayList<MediaBrowser.MediaItem>();
 
         if (children == null) {
             Log.e(TAG, "Error: children are null in getFolderItemsFilterAttr");
@@ -571,21 +603,21 @@
         }
 
         /* check for index out of bound errors */
-        result_items = checkIndexOutofBounds(bdaddr, children, startItem, endItem);
-        if (result_items == null) {
-            Log.w(TAG, "result_items is null.");
+        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(result_items.size());
+        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 < result_items.size(); itemIndex++) {
+        for (int itemIndex = 0; itemIndex < resultItems.size(); itemIndex++) {
             /* item type. Needs to be set by media player */
-            MediaBrowser.MediaItem item = result_items.get(itemIndex);
+            MediaBrowser.MediaItem item = resultItems.get(itemIndex);
             int flags = item.getFlags();
             if ((flags & MediaBrowser.MediaItem.FLAG_BROWSABLE) != 0) {
                 folderDataNative.mItemTypes[itemIndex] = AvrcpConstants.BTRC_ITEM_FOLDER;
@@ -629,9 +661,9 @@
                     /* check if media player provided requested attributes */
                     String value = null;
 
-                    int attribId = isAllAttribRequested ? (idx + 1) :
-                            mFolderItemsReqObj.mAttrIDs[idx];
-                    value = getAttrValue(attribId, result_items.get(itemIndex));
+                    int attribId =
+                            isAllAttribRequested ? (idx + 1) : mFolderItemsReqObj.mAttrIDs[idx];
+                    value = getAttrValue(attribId, resultItems.get(itemIndex));
                     if (value != null) {
                         attrArray.add(value);
                         attrId.add(attribId);
@@ -646,17 +678,20 @@
         /* 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++)
+            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);
+        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);
     }
 
@@ -712,7 +747,9 @@
                 return null;
             }
         }
-        if (DEBUG) Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + "attr id:" + attr);
+        if (DEBUG) {
+            Log.d(TAG, "getAttrValue: attrvalue = " + attrValue + "attr id:" + attr);
+        }
         return attrValue;
     }
 
@@ -726,10 +763,11 @@
     /* 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))
+            if (item.getMediaId().equals(uid) && (
+                    (item.getFlags() & MediaBrowser.MediaItem.FLAG_BROWSABLE)
+                            == MediaBrowser.MediaItem.FLAG_BROWSABLE)) {
                 return true;
+            }
         }
         return false;
     }
@@ -790,14 +828,14 @@
         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;
+        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/avrcp/mockable/MediaBrowser.java b/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
new file mode 100644
index 0000000..d6e846d
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
@@ -0,0 +1,165 @@
+/*
+ * 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.avrcp;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provide a mockable interface in order to test classes that use MediaBrowser.
+ * We need this class due to the fact that the MediaController class is marked as final and
+ * there is no way to currently mock final classes in Android. Once this is possible this class
+ * can be deleted.
+ */
+public class MediaBrowser {
+    android.media.browse.MediaBrowser mDelegate;
+
+    /**
+     * Wrap a real MediaBrowser object
+     */
+    public MediaBrowser(android.media.browse.MediaBrowser delegate) {
+        mDelegate = delegate;
+    }
+
+    /**
+     * Create a real MediaBrowser object and wrap it
+     */
+    public MediaBrowser(Context context, ComponentName serviceComponent,
+            ConnectionCallback callback, Bundle rootHints) {
+        mDelegate = new android.media.browse.MediaBrowser(context, serviceComponent, callback,
+                rootHints);
+    }
+
+    /**
+     * Wrapper for MediaBrowser.ConnectionCallback
+     */
+    public abstract static class ConnectionCallback extends
+            android.media.browse.MediaBrowser.ConnectionCallback {}
+
+    /**
+     * Wrapper for MediaBrowser.ItemCallback
+     */
+    public abstract static class ItemCallback extends
+            android.media.browse.MediaBrowser.ItemCallback {}
+
+    /**
+     * Wrapper for MediaBrowser.SubscriptionCallback
+     */
+    public abstract static class SubscriptionCallback extends
+            android.media.browse.MediaBrowser.SubscriptionCallback {}
+
+    /**
+     * Wrapper for MediaBrowser.connect()
+     */
+    public void connect() {
+        mDelegate.connect();
+    }
+
+    /**
+     * Wrapper for MediaBrowser.disconnect()
+     */
+    public void disconnect() {
+        mDelegate.disconnect();
+    }
+
+    /**
+     * Wrapper for MediaBrowser.getExtras()
+     */
+    public Bundle getExtras() {
+        return mDelegate.getExtras();
+    }
+
+    /**
+     * Wrapper for MediaBrowser.getItem(String mediaId, ItemCallback callback)
+     */
+    public void getItem(String mediaId, ItemCallback callback) {
+        mDelegate.getItem(mediaId, callback);
+    }
+
+    /**
+     * Wrapper for MediaBrowser.getRoot()
+     */
+    public String getRoot() {
+        return mDelegate.getRoot();
+    }
+
+    /**
+     * Wrapper for MediaBrowser.getServiceComponent()
+     */
+    public ComponentName getServiceComponent() {
+        return mDelegate.getServiceComponent();
+    }
+
+    /**
+     * Wrapper for MediaBrowser.getSessionToken()
+     */
+    public MediaSession.Token getSessionToken() {
+        return mDelegate.getSessionToken();
+    }
+    /**
+     * Wrapper for MediaBrowser.isConnected()
+     */
+    public boolean isConnected() {
+        return mDelegate.isConnected();
+    }
+
+    /**
+     * Wrapper for MediaBrowser.subscribe(String parentId, Bundle options,
+     * SubscriptionCallback callback)
+     */
+    public void subscribe(String parentId, Bundle options, SubscriptionCallback callback) {
+        mDelegate.subscribe(parentId, options, callback);
+    }
+
+    /**
+     * Wrapper for MediaBrowser.subscribe(String parentId, SubscriptionCallback callback)
+     */
+    public void subscribe(String parentId, SubscriptionCallback callback) {
+        mDelegate.subscribe(parentId, callback);
+    }
+
+    /**
+     * Wrapper for MediaBrowser.unsubscribe(String parentId)
+     */
+    public void unsubscribe(String parentId) {
+        mDelegate.unsubscribe(parentId);
+    }
+
+
+    /**
+     * Wrapper for MediaBrowser.unsubscribe(String parentId, SubscriptionCallback callback)
+     */
+    public void unsubscribe(String parentId, SubscriptionCallback callback) {
+        mDelegate.unsubscribe(parentId, callback);
+    }
+
+    /**
+     * A function that allows Mockito to capture the constructor arguments when using
+     * MediaBrowserFactory.make()
+     */
+    @VisibleForTesting
+    public void testInit(Context context, ComponentName serviceComponent,
+            ConnectionCallback callback, Bundle rootHints) {
+        // This is only used by Mockito to capture the constructor arguments on creation
+        Log.wtfStack("AvrcpMockMediaBrowser", "This function should never be called");
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaBrowserFactory.java b/src/com/android/bluetooth/avrcp/mockable/MediaBrowserFactory.java
new file mode 100644
index 0000000..eb8c092
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaBrowserFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.avrcp;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provide a method to inject custom MediaBrowser objects for testing. By using the factory
+ * methods instead of calling the constructor of MediaBrowser directly, we can inject a custom
+ * MediaBrowser that can be used with JUnit and Mockito to set expectations and validate
+ * behaviour in tests.
+ */
+public final class MediaBrowserFactory {
+    private static MediaBrowser sInjectedBrowser;
+
+    static MediaBrowser wrap(android.media.browse.MediaBrowser delegate) {
+        if (sInjectedBrowser != null) return sInjectedBrowser;
+        return (delegate != null) ? new MediaBrowser(delegate) : null;
+    }
+
+    static MediaBrowser make(Context context, ComponentName serviceComponent,
+            MediaBrowser.ConnectionCallback callback, Bundle rootHints) {
+        if (sInjectedBrowser != null) {
+            sInjectedBrowser.testInit(context, serviceComponent, callback, rootHints);
+            return sInjectedBrowser;
+        }
+        return new MediaBrowser(context, serviceComponent, callback, rootHints);
+    }
+
+    @VisibleForTesting
+    static void inject(MediaBrowser browser) {
+        sInjectedBrowser = browser;
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaController.java b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
index 00fb1aa..934a454 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaController.java
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
@@ -1,55 +1,67 @@
+/*
+ * 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.avrcp;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.content.pm.ParceledListSlice;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.MediaMetadata;
 import android.media.MediaDescription;
+import android.media.MediaMetadata;
 import android.media.Rating;
-import android.media.VolumeProvider;
-import android.media.session.PlaybackState;
 import android.media.session.MediaSession;
-import android.media.session.MediaSession.QueueItem;
+import android.media.session.PlaybackState;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.text.TextUtils;
-import android.util.Log;
 import android.view.KeyEvent;
 
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * Provide a mockable interface in order to test classes that use MediaController.
+ * We need this class due to the fact that the MediaController class is marked as final and
+ * there is no way to currently mock final classes in Android. Once this is possible this class
+ * can be deleted.
+ */
 public class MediaController {
-    public @NonNull android.media.session.MediaController mDelegate;
+    @NonNull public android.media.session.MediaController mDelegate;
     public android.media.session.MediaController.TransportControls mTransportDelegate;
     public TransportControls mTransportControls;
 
-    @Nullable
-    public static MediaController wrap(@Nullable android.media.session.MediaController delegate) {
-      return (delegate != null) ? new MediaController(delegate) : null;
-    }
-
     public MediaController(@NonNull android.media.session.MediaController delegate) {
         mDelegate = delegate;
         mTransportDelegate = delegate.getTransportControls();
         mTransportControls = new TransportControls();
     }
 
+    public MediaController(Context context, MediaSession.Token token) {
+        mDelegate = new android.media.session.MediaController(context, token);
+        mTransportDelegate = mDelegate.getTransportControls();
+        mTransportControls = new TransportControls();
+    }
+
     public android.media.session.MediaController getWrappedInstance() {
         return mDelegate;
     }
 
-    public @NonNull TransportControls getTransportControls() {
+    @NonNull
+    public TransportControls getTransportControls() {
         return mTransportControls;
     }
 
@@ -57,23 +69,28 @@
         return mDelegate.dispatchMediaButtonEvent(keyEvent);
     }
 
-    public @Nullable PlaybackState getPlaybackState() {
+    @Nullable
+    public PlaybackState getPlaybackState() {
         return mDelegate.getPlaybackState();
     }
 
-    public @Nullable MediaMetadata getMetadata() {
+    @Nullable
+    public MediaMetadata getMetadata() {
         return mDelegate.getMetadata();
     }
 
-    public @Nullable List<MediaSession.QueueItem> getQueue() {
+    @Nullable
+    public List<MediaSession.QueueItem> getQueue() {
         return mDelegate.getQueue();
     }
 
-    public @Nullable CharSequence getQueueTitle() {
+    @Nullable
+    public CharSequence getQueueTitle() {
         return mDelegate.getQueueTitle();
     }
 
-    public @Nullable Bundle getExtras() {
+    @Nullable
+    public Bundle getExtras() {
         return mDelegate.getExtras();
     }
 
@@ -85,15 +102,18 @@
         return mDelegate.getFlags();
     }
 
-    public @Nullable android.media.session.MediaController.PlaybackInfo getPlaybackInfo() {
+    @Nullable
+    public android.media.session.MediaController.PlaybackInfo getPlaybackInfo() {
         return mDelegate.getPlaybackInfo();
     }
 
-    public @Nullable PendingIntent getSessionActivity() {
+    @Nullable
+    public PendingIntent getSessionActivity() {
         return mDelegate.getSessionActivity();
     }
 
-    public @NonNull MediaSession.Token getSessionToken() {
+    @NonNull
+    public MediaSession.Token getSessionToken() {
         return mDelegate.getSessionToken();
     }
 
@@ -155,11 +175,11 @@
     public String toString() {
         MediaMetadata data = getMetadata();
         MediaDescription desc = (data == null) ? null : data.getDescription();
-        return "MediaController (" + getPackageName() + "@"
-                + Integer.toHexString(mDelegate.hashCode()) + ") " + desc;
+        return "MediaController (" + getPackageName() + "@" + Integer.toHexString(
+                mDelegate.hashCode()) + ") " + desc;
     }
 
-    public static abstract class Callback extends android.media.session.MediaController.Callback { }
+    public abstract static class Callback extends android.media.session.MediaController.Callback {}
 
     public class TransportControls {
 
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaControllerFactory.java b/src/com/android/bluetooth/avrcp/mockable/MediaControllerFactory.java
new file mode 100644
index 0000000..8b2a80d
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaControllerFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.avrcp;
+
+import android.content.Context;
+import android.media.session.MediaSession;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provide a method to inject custom MediaController objects for testing. By using the factory
+ * methods instead of calling the constructor of MediaController directly, we can inject a custom
+ * MediaController that can be used with JUnit and Mockito to set expectations and validate
+ * behaviour in tests.
+ */
+public final class MediaControllerFactory {
+    private static MediaController sInjectedController;
+
+    static MediaController wrap(android.media.session.MediaController delegate) {
+        if (sInjectedController != null) return sInjectedController;
+        return (delegate != null) ? new MediaController(delegate) : null;
+    }
+
+    static MediaController make(Context context, MediaSession.Token token) {
+        if (sInjectedController != null) return sInjectedController;
+        return new MediaController(context, token);
+    }
+
+    @VisibleForTesting
+    static void inject(MediaController controller) {
+        sInjectedController = controller;
+    }
+}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 7ff0a25..6111ec4 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -21,16 +21,13 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothAvrcpController;
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.browse.MediaBrowser;
-import android.media.browse.MediaBrowser.MediaItem;
 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.Looper;
 import android.os.Message;
 import android.util.Log;
 
@@ -39,9 +36,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.UUID;
 
 /**
@@ -49,17 +44,17 @@
  */
 public class AvrcpControllerService extends ProfileService {
     static final String TAG = "AvrcpControllerService";
-    static final boolean DBG = true;
-    static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+    static final boolean DBG = false;
+    static final boolean VDBG = false;
     /*
      *  Play State Values from JNI
      */
     private static final byte JNI_PLAY_STATUS_STOPPED = 0x00;
     private static final byte JNI_PLAY_STATUS_PLAYING = 0x01;
-    private static final byte JNI_PLAY_STATUS_PAUSED  = 0x02;
+    private static final byte JNI_PLAY_STATUS_PAUSED = 0x02;
     private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03;
     private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
-    private static final byte JNI_PLAY_STATUS_ERROR    = -1;
+    private static final byte JNI_PLAY_STATUS_ERROR = -1;
 
     /*
      * Browsing Media Item Attribute IDs
@@ -110,7 +105,7 @@
      * receive.
      */
     public static final String ACTION_BROWSE_CONNECTION_STATE_CHANGED =
-        "android.bluetooth.avrcp-controller.profile.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
@@ -124,7 +119,7 @@
      * </ul>
      */
     public static final String ACTION_TRACK_EVENT =
-        "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
+            "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
 
     /**
      * Intent used to broadcast the change of folder list.
@@ -136,14 +131,14 @@
      * </ul>
      */
     public static final String ACTION_FOLDER_LIST =
-        "android.bluetooth.avrcp-controller.profile.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";
+            "com.android.bluetooth.avrcp-controller.EXTRA_FOLDER_BT_ID";
 
     public static final String EXTRA_METADATA =
             "android.bluetooth.avrcp-controller.profile.extra.METADATA";
@@ -209,14 +204,12 @@
         initNative();
     }
 
-    protected String getName() {
-        return TAG;
-    }
-
+    @Override
     protected IProfileServiceBinder initBinder() {
         return new BluetoothAvrcpControllerBinder(this);
     }
 
+    @Override
     protected boolean start() {
         HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
         thread.start();
@@ -227,7 +220,9 @@
         return true;
     }
 
+    @Override
     protected boolean stop() {
+        setAvrcpControllerService(null);
         if (mAvrcpCtSm != null) {
             mAvrcpCtSm.doQuit();
         }
@@ -237,42 +232,22 @@
     //API Methods
 
     public static synchronized AvrcpControllerService getAvrcpControllerService() {
-        if (sAvrcpControllerService != null && sAvrcpControllerService.isAvailable()) {
-            if (DBG) {
-                Log.d(TAG, "getAvrcpControllerService(): returning "
-                    + sAvrcpControllerService);
-            }
-            return sAvrcpControllerService;
+        if (sAvrcpControllerService == null) {
+            Log.w(TAG, "getAvrcpControllerService(): service is null");
+            return null;
         }
-        if (DBG) {
-            if (sAvrcpControllerService == null) {
-                Log.d(TAG, "getAvrcpControllerService(): service is NULL");
-            } else if (!(sAvrcpControllerService.isAvailable())) {
-                Log.d(TAG, "getAvrcpControllerService(): service is not available");
-            }
+        if (!sAvrcpControllerService.isAvailable()) {
+            Log.w(TAG, "getAvrcpControllerService(): service is not available ");
+            return null;
         }
-        return null;
+        return sAvrcpControllerService;
     }
 
     private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) {
-                Log.d(TAG, "setAvrcpControllerService(): set to: " + sAvrcpControllerService);
-            }
-            sAvrcpControllerService = instance;
-        } else {
-            if (DBG) {
-                if (instance == null) {
-                    Log.d(TAG, "setAvrcpControllerService(): service not available");
-                } else if (!instance.isAvailable()) {
-                    Log.d(TAG, "setAvrcpControllerService(): service is cleaning up");
-                }
-            }
+        if (DBG) {
+            Log.d(TAG, "setAvrcpControllerService(): set to: " + instance);
         }
-    }
-
-    private static synchronized void clearAvrcpControllerService() {
-        sAvrcpControllerService = null;
+        sAvrcpControllerService = instance;
     }
 
     public synchronized List<BluetoothDevice> getConnectedDevices() {
@@ -300,11 +275,12 @@
 
     public synchronized int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return (mConnectedDevice != null ? BluetoothProfile.STATE_CONNECTED :
-            BluetoothProfile.STATE_DISCONNECTED);
+        return (mConnectedDevice != null ? BluetoothProfile.STATE_CONNECTED
+                : BluetoothProfile.STATE_DISCONNECTED);
     }
 
-    public synchronized void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+    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");
@@ -315,8 +291,9 @@
             return;
         }
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
-            MESSAGE_SEND_GROUP_NAVIGATION_CMD, keyCode, keyState, device);
+        Message msg = mAvrcpCtSm.obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_SEND_GROUP_NAVIGATION_CMD,
+                keyCode, keyState, device);
         mAvrcpCtSm.sendMessage(msg);
     }
 
@@ -333,20 +310,20 @@
         }
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg = mAvrcpCtSm
-            .obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
-                keyCode, keyState, device);
+        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();
+        mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS)
+                .sendToTarget();
     }
 
     public void stopAvrcpUpdates() {
-        mAvrcpCtSm.obtainMessage(
-            AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS).sendToTarget();
+        mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS)
+                .sendToTarget();
     }
 
     public synchronized MediaMetadata getMetaData(BluetoothDevice device) {
@@ -432,8 +409,8 @@
      * 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) {
+    public synchronized boolean getChildren(BluetoothDevice device, String parentMediaId, int start,
+            int items) {
         if (DBG) {
             Log.d(TAG, "getChildren device = " + device + " parent " + parentMediaId);
         }
@@ -444,8 +421,7 @@
         }
 
         if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getChildren device " + device + " does not match " +
-                mConnectedDevice);
+            Log.e(TAG, "getChildren device " + device + " does not match " + mConnectedDevice);
             return false;
         }
 
@@ -461,11 +437,11 @@
         return true;
     }
 
-    public synchronized boolean getNowPlayingList(
-            BluetoothDevice device, String id, int start, int items) {
+    public synchronized boolean getNowPlayingList(BluetoothDevice device, String id, int start,
+            int items) {
         if (DBG) {
-            Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start +
-                "items = " + items);
+            Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start + "items = "
+                    + items);
         }
 
         if (device == null) {
@@ -474,8 +450,8 @@
         }
 
         if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "getNowPlayingList device " + device + " does not match " +
-                mConnectedDevice);
+            Log.e(TAG,
+                    "getNowPlayingList device " + device + " does not match " + mConnectedDevice);
             return false;
         }
 
@@ -486,17 +462,18 @@
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        Message msg = mAvrcpCtSm.obtainMessage(
-            AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, items, id);
+        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) {
+    public synchronized boolean getFolderList(BluetoothDevice device, String id, int start,
+            int items) {
         if (DBG) {
-            Log.d(TAG, "getFolderListing device = " + device + " start = " + start +
-                "items = " + items);
+            Log.d(TAG, "getFolderListing device = " + device + " start = " + start + "items = "
+                    + items);
         }
 
         if (device == null) {
@@ -516,16 +493,17 @@
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        Message msg = mAvrcpCtSm.obtainMessage(
-            AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start, items, id);
+        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);
+            Log.d(TAG,
+                    "getPlayerList device = " + device + " start = " + start + "items = " + items);
         }
 
         if (device == null) {
@@ -545,17 +523,18 @@
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        Message msg = mAvrcpCtSm.obtainMessage(
-            AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
+        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) {
+    public synchronized boolean changeFolderPath(BluetoothDevice device, int direction, String uid,
+            String fid) {
         if (DBG) {
-            Log.d(TAG, "changeFolderPath device = " + device + " direction " +
-                direction + " uid " + uid);
+            Log.d(TAG, "changeFolderPath device = " + device + " direction " + direction + " uid "
+                    + uid);
         }
 
         if (device == null) {
@@ -564,8 +543,7 @@
         }
 
         if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "changeFolderPath device " + device + " does not match " +
-                mConnectedDevice);
+            Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
             return false;
         }
 
@@ -579,8 +557,9 @@
         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);
+        Message msg =
+                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH,
+                        direction, 0, b);
         mAvrcpCtSm.sendMessage(msg);
         return true;
     }
@@ -596,8 +575,7 @@
         }
 
         if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "changeFolderPath device " + device + " does not match " +
-                mConnectedDevice);
+            Log.e(TAG, "changeFolderPath device " + device + " does not match " + mConnectedDevice);
             return false;
         }
 
@@ -608,8 +586,9 @@
 
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        Message msg = mAvrcpCtSm.obtainMessage(
-            AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id, 0, fid);
+        Message msg =
+                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id,
+                        0, fid);
         mAvrcpCtSm.sendMessage(msg);
         return true;
     }
@@ -625,8 +604,8 @@
         }
 
         if (!device.equals(mConnectedDevice)) {
-            Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match " +
-                mConnectedDevice);
+            Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match "
+                    + mConnectedDevice);
             return;
         }
 
@@ -639,7 +618,7 @@
 
     //Binder object: Must be static class or memory leak may occur
     private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
-        implements IProfileServiceBinder {
+            implements IProfileServiceBinder {
 
         private AvrcpControllerService mService;
 
@@ -659,9 +638,9 @@
             mService = svc;
         }
 
-        public boolean cleanup() {
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
         @Override
@@ -690,7 +669,7 @@
             }
 
             if (device == null) {
-              throw new IllegalStateException("Device cannot be null!");
+                throw new IllegalStateException("Device cannot be null!");
             }
 
             return service.getConnectionState(device);
@@ -705,7 +684,7 @@
             }
 
             if (device == null) {
-              throw new IllegalStateException("Device cannot be null!");
+                throw new IllegalStateException("Device cannot be null!");
             }
 
             service.sendGroupNavigationCmd(device, keyCode, keyState);
@@ -720,7 +699,7 @@
             }
 
             if (device == null) {
-              throw new IllegalStateException("Device cannot be null!");
+                throw new IllegalStateException("Device cannot be null!");
             }
 
             return service.getPlayerSettings(device);
@@ -739,8 +718,9 @@
 
     // 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);
+        Log.d(TAG,
+                "passthrough response received as: key: " + id + " state: " + keyState + "address:"
+                        + address);
     }
 
     private void handleGroupNavigationRsp(int id, int keyState) {
@@ -748,23 +728,23 @@
     }
 
     // Called by JNI when a device has connected or disconnected.
-    private synchronized void onConnectionStateChanged(
-            boolean rc_connected, boolean br_connected, byte[] address) {
+    private synchronized void onConnectionStateChanged(boolean rcConnected, boolean brConnected,
+            byte[] address) {
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
-        Log.d(TAG, "onConnectionStateChanged " + rc_connected + " " + br_connected +
-            device + " conn device " + mConnectedDevice);
+        Log.d(TAG, "onConnectionStateChanged " + rcConnected + " " + brConnected + device
+                + " conn device " + mConnectedDevice);
         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 = (rc_connected ? BluetoothProfile.STATE_CONNECTED :
-            BluetoothProfile.STATE_DISCONNECTED);
+        int oldState = (device.equals(mConnectedDevice) ? BluetoothProfile.STATE_CONNECTED
+                : BluetoothProfile.STATE_DISCONNECTED);
+        int newState = (rcConnected ? BluetoothProfile.STATE_CONNECTED
+                : BluetoothProfile.STATE_DISCONNECTED);
 
-        if (rc_connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
+        if (rcConnected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
             /* AVRCPControllerService supports single connection */
             if (mConnectedDevice != null) {
                 Log.d(TAG, "A Connection already exists, returning");
@@ -772,23 +752,23 @@
             }
             mConnectedDevice = device;
             Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
-                oldState, device);
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
+                    oldState, device);
             mAvrcpCtSm.sendMessage(msg);
-        } else if (!rc_connected && oldState == BluetoothProfile.STATE_CONNECTED) {
+        } else if (!rcConnected && oldState == BluetoothProfile.STATE_CONNECTED) {
             mConnectedDevice = null;
             Message msg = mAvrcpCtSm.obtainMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
-                oldState, device);
+                    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 (rc_connected && br_connected) {
+        if (rcConnected && brConnected) {
             mBrowseConnected = true;
             Message msg = mAvrcpCtSm.obtainMessage(
-               AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE);
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE);
             msg.arg1 = 1;
             msg.obj = device;
             mAvrcpCtSm.sendMessage(msg);
@@ -798,8 +778,9 @@
     // 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);
+        Message msg =
+                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES,
+                        features, 0, device);
         mAvrcpCtSm.sendMessage(msg);
     }
 
@@ -816,8 +797,9 @@
             Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
             return;
         }
-        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
-            MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, (int) label, 0);
+        Message msg = mAvrcpCtSm.obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION,
+                (int) label, 0);
         mAvrcpCtSm.sendMessage(msg);
     }
 
@@ -830,13 +812,13 @@
             return;
         }
         Message msg = mAvrcpCtSm.obtainMessage(
-            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
         mAvrcpCtSm.sendMessage(msg);
     }
 
     // Called by JNI when a track changes and local AvrcpController is registered for updates.
     private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
-        String[] attribVals) {
+            String[] attribVals) {
         if (DBG) {
             Log.d(TAG, "onTrackChanged");
         }
@@ -852,16 +834,17 @@
         }
         List<String> attrValList = Arrays.asList(attribVals);
         TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
-        if (DBG) {
+        if (VDBG) {
             Log.d(TAG, "onTrackChanged " + trackInfo);
         }
-        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
-            MESSAGE_PROCESS_TRACK_CHANGED, 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
-    private synchronized void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) {
+    private synchronized void onPlayPositionChanged(byte[] address, int songLen,
+            int currSongPosition) {
         if (DBG) {
             Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
         }
@@ -870,8 +853,9 @@
             Log.e(TAG, "onPlayPositionChanged not found device not found " + address);
             return;
         }
-        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
-            MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition);
+        Message msg = mAvrcpCtSm.obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
+                songLen, currSongPosition);
         mAvrcpCtSm.sendMessage(msg);
     }
 
@@ -888,10 +872,10 @@
         int playbackState = PlaybackState.STATE_NONE;
         switch (playStatus) {
             case JNI_PLAY_STATUS_STOPPED:
-                playbackState =  PlaybackState.STATE_STOPPED;
+                playbackState = PlaybackState.STATE_STOPPED;
                 break;
             case JNI_PLAY_STATUS_PLAYING:
-                playbackState =  PlaybackState.STATE_PLAYING;
+                playbackState = PlaybackState.STATE_PLAYING;
                 break;
             case JNI_PLAY_STATUS_PAUSED:
                 playbackState = PlaybackState.STATE_PAUSED;
@@ -905,13 +889,14 @@
             default:
                 playbackState = PlaybackState.STATE_NONE;
         }
-        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
-            MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
+        Message msg = mAvrcpCtSm.obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
         mAvrcpCtSm.sendMessage(msg);
     }
 
     // Called by JNI to report remote Player's capabilities
-    private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) {
+    private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp,
+            int rspLen) {
         if (DBG) {
             Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
         }
@@ -920,12 +905,13 @@
             Log.e(TAG, "handlePlayerAppSetting not found device not found " + address);
             return;
         }
-        PlayerApplicationSettings supportedSettings = PlayerApplicationSettings.
-            makeSupportedSettings(playerAttribRsp);
+        PlayerApplicationSettings supportedSettings =
+                PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
         /* Do nothing */
     }
 
-    private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) {
+    private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
+            int rspLen) {
         if (DBG) {
             Log.d(TAG, "onPlayerAppSettingChanged ");
         }
@@ -934,30 +920,30 @@
             Log.e(TAG, "onPlayerAppSettingChanged not found device not found " + address);
             return;
         }
-        PlayerApplicationSettings desiredSettings = PlayerApplicationSettings.
-            makeSettings(playerAttribRsp);
+        PlayerApplicationSettings desiredSettings =
+                PlayerApplicationSettings.makeSettings(playerAttribRsp);
         /* Do nothing */
     }
 
     // Browsing related JNI callbacks.
     void handleGetFolderItemsRsp(int status, MediaItem[] items) {
         if (DBG) {
-            Log.d(TAG, "handleGetFolderItemsRsp called with status " + status +
-                " items "  + items.length + " items.");
+            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);
+            Message msg = mAvrcpCtSm.obtainMessage(
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
             mAvrcpCtSm.sendMessage(msg);
             return;
         }
 
         for (MediaItem item : items) {
-            if (DBG) {
+            if (VDBG) {
                 Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId());
             }
         }
@@ -965,8 +951,8 @@
         for (MediaItem item : items) {
             itemsList.add(item);
         }
-        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
-            MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList);
+        Message msg = mAvrcpCtSm.obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList);
         mAvrcpCtSm.sendMessage(msg);
     }
 
@@ -975,7 +961,7 @@
             Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
         }
         for (AvrcpPlayer item : items) {
-            if (DBG) {
+            if (VDBG) {
                 Log.d(TAG, "bt player item: " + item);
             }
         }
@@ -984,17 +970,17 @@
             itemsList.add(p);
         }
 
-        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
-            MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
+        Message msg = mAvrcpCtSm.obtainMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
         mAvrcpCtSm.sendMessage(msg);
     }
 
     // JNI Helper functions to convert native objects to java.
-    MediaItem createFromNativeMediaItem(
-            byte[] uid, int type, String name, int[] attrIds, String[] attrVals) {
-        if (DBG) {
-            Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " +
-                name + " attrids " + attrIds + " attrVals " + attrVals);
+    MediaItem createFromNativeMediaItem(byte[] 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);
         }
         MediaDescription.Builder mdb = new MediaDescription.Builder();
 
@@ -1015,11 +1001,10 @@
         return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE);
     }
 
-    MediaItem createFromNativeFolderItem(
-            byte[] uid, int type, String name, int playable) {
-        if (DBG) {
-            Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type +
-                " name " + name + " playable " + playable);
+    MediaItem createFromNativeFolderItem(byte[] uid, int type, String name, int playable) {
+        if (VDBG) {
+            Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name " + name
+                    + " playable " + playable);
         }
         MediaDescription.Builder mdb = new MediaDescription.Builder();
 
@@ -1039,12 +1024,12 @@
         return new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE);
     }
 
-    AvrcpPlayer createFromNativePlayerItem(
-            int id, String name, byte[] transportFlags, int playStatus, int playerType) {
-        if (DBG) {
-            Log.d(TAG, "createFromNativePlayerItem name: " + name + " transportFlags " +
-                transportFlags + " play status " + playStatus + " player type " +
-                playerType);
+    AvrcpPlayer createFromNativePlayerItem(int id, String name, byte[] transportFlags,
+            int playStatus, int playerType) {
+        if (VDBG) {
+            Log.d(TAG,
+                    "createFromNativePlayerItem name: " + name + " transportFlags " + transportFlags
+                            + " play status " + playStatus + " player type " + playerType);
         }
         AvrcpPlayer player = new AvrcpPlayer(id, name, 0, playStatus, playerType);
         return player;
@@ -1054,8 +1039,9 @@
         if (DBG) {
             Log.d(TAG, "handleChangeFolderRsp count: " + count);
         }
-        Message msg = mAvrcpCtSm.obtainMessage(
-            AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count);
+        Message msg =
+                mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
+                        count);
         mAvrcpCtSm.sendMessage(msg);
     }
 
@@ -1064,7 +1050,7 @@
             Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
         }
         Message msg = mAvrcpCtSm.obtainMessage(
-            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
         mAvrcpCtSm.sendMessage(msg);
     }
 
@@ -1073,7 +1059,7 @@
             Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
         }
         Message msg = mAvrcpCtSm.obtainMessage(
-            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
         mAvrcpCtSm.sendMessage(msg);
     }
 
@@ -1103,45 +1089,51 @@
         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));
+            data[i / 2] = (byte) ((Character.digit(uidStr.charAt(i), 16) << 4) + Character.digit(
+                    uidStr.charAt(i + 1), 16));
         }
         return data;
     }
 
-    private native static void classInitNative();
+    private static native void classInitNative();
 
     private native void initNative();
 
     private native void cleanupNative();
 
-    native static boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
+    static native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
 
-    native static boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
-        int keyState);
+    static native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
+            int keyState);
 
-    native static void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
-        byte[] atttibIds, byte[] attribVal);
+    static native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
+            byte[] atttibIds, byte[] attribVal);
 
     /* This api is used to send response to SET_ABS_VOL_CMD */
-    native static void sendAbsVolRspNative(byte[] address, int absVol, int label);
+    static native void sendAbsVolRspNative(byte[] address, int absVol, int label);
 
     /* This api is used to inform remote for any volume level changes */
-    native static void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
-        int label);
+    static native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
+            int label);
 
     /* API used to fetch the playback state */
-    native static void getPlaybackStateNative(byte[] address);
+    static native void getPlaybackStateNative(byte[] address);
+
     /* API used to fetch the current now playing list */
-    native static void getNowPlayingListNative(byte[] address, byte start, byte end);
+    static native void getNowPlayingListNative(byte[] address, int start, int end);
+
     /* API used to fetch the current folder's listing */
-    native static void getFolderListNative(byte[] address, byte start, byte end);
+    static native void getFolderListNative(byte[] address, int start, int end);
+
     /* API used to fetch the listing of players */
-    native static void getPlayerListNative(byte[] address, byte start, byte end);
+    static native void getPlayerListNative(byte[] address, int start, int end);
+
     /* API used to change the folder */
-    native static void changeFolderPathNative(byte[] address, byte direction, byte[] uid);
-    native static void playItemNative(
-        byte[] address, byte scope, byte[] uid, int uidCounter);
-    native static void setBrowsedPlayerNative(byte[] address, int playerId);
-    native static void setAddressedPlayerNative(byte[] address, int playerId);
+    static native void changeFolderPathNative(byte[] address, byte direction, byte[] uid);
+
+    static native void playItemNative(byte[] address, byte scope, byte[] uid, int uidCounter);
+
+    static native void setBrowsedPlayerNative(byte[] address, int playerId);
+
+    static 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 e5364f9..3077664 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -25,25 +25,24 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.media.browse.MediaBrowser;
-import android.media.browse.MediaBrowser.MediaItem;
 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.util.Log;
 
+import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Queue;
 
 /**
  * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections
@@ -88,10 +87,14 @@
     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;
+    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 5 items at a time.
-    static final int GET_FOLDER_ITEMS_PAGINATION_SIZE = 5;
+    // 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,7 +110,7 @@
 
     private static final String TAG = "AvrcpControllerSM";
     private static final boolean DBG = true;
-    private static final boolean VDBG = true;
+    private static final boolean VDBG = false;
 
     private final Context mContext;
     private final AudioManager mAudioManager;
@@ -122,8 +125,8 @@
     private final MoveToRoot mMoveToRoot;
 
     private final Object mLock = new Object();
-    private static final ArrayList<MediaItem> mEmptyMediaItemList = new ArrayList<>();
-    private static final MediaMetadata mEmptyMMD = new MediaMetadata.Builder().build();
+    private static final ArrayList<MediaItem> EMPTY_MEDIA_ITEM_LIST = new ArrayList<>();
+    private static final MediaMetadata EMPTY_MEDIA_METADATA = new MediaMetadata.Builder().build();
 
     // APIs exist to access these so they must be thread safe
     private Boolean mIsConnected = false;
@@ -131,9 +134,8 @@
     private AvrcpPlayer mAddressedPlayer;
 
     // Only accessed from State Machine processMessage
-    private boolean mAbsoluteVolumeChangeInProgress = false;
-    private boolean mBroadcastMetadata = false;
-    private int previousPercentageVol = -1;
+    private int mVolumeChangedNotificationsToIgnore = 0;
+    private int mPreviousPercentageVol = -1;
 
     // Depth from root of current browsing. This can be used to move to root directly.
     private int mBrowseDepth = 0;
@@ -181,31 +183,34 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
+            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) {
+                        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);
+                                BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
                         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
-                            BluetoothProfile.STATE_DISCONNECTED);
+                                BluetoothProfile.STATE_DISCONNECTED);
                         intent.putExtra(BluetoothProfile.EXTRA_STATE,
-                            BluetoothProfile.STATE_CONNECTED);
+                                BluetoothProfile.STATE_CONNECTED);
                         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
                         mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
                     }
                     break;
 
                 default:
-                    Log.w(TAG,"Currently Disconnected not handling " + dumpMessageString(msg.what));
+                    Log.w(TAG,
+                            "Currently Disconnected not handling " + dumpMessageString(msg.what));
                     return false;
             }
             return true;
@@ -215,40 +220,23 @@
     class Connected extends State {
         @Override
         public boolean processMessage(Message msg) {
-            Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
+            if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what));
             A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
             synchronized (mLock) {
                 switch (msg.what) {
-                    case MESSAGE_STOP_METADATA_BROADCASTS:
-                        mBroadcastMetadata = false;
-                        broadcastPlayBackStateChanged(new PlaybackState.Builder().setState(
-                            PlaybackState.STATE_PAUSED, mAddressedPlayer.getPlayTime(),
-                            0).build());
-                        break;
-
-                    case MESSAGE_START_METADATA_BROADCASTS:
-                        mBroadcastMetadata = true;
-                        broadcastPlayBackStateChanged(mAddressedPlayer.getPlaybackState());
-                        if (mAddressedPlayer.getCurrentTrack() != null) {
-                            broadcastMetaDataChanged(
-                                mAddressedPlayer.getCurrentTrack().getMediaMetaData());
-                        }
-                        break;
-
                     case MESSAGE_SEND_PASS_THROUGH_CMD:
                         BluetoothDevice device = (BluetoothDevice) msg.obj;
-                        AvrcpControllerService
-                            .sendPassThroughCommandNative(Utils.getByteAddress(device), msg.arg1,
-                                msg.arg2);
+                        AvrcpControllerService.sendPassThroughCommandNative(
+                                Utils.getByteAddress(device), msg.arg1, msg.arg2);
                         if (a2dpSinkService != null) {
-                            Log.d(TAG, " inform AVRCP Commands to A2DP Sink ");
+                            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);
+                                mRemoteDevice.getBluetoothAddress(), msg.arg1, msg.arg2);
                         break;
 
                     case MESSAGE_GET_NOW_PLAYING_LIST:
@@ -269,8 +257,8 @@
 
                     case MESSAGE_GET_PLAYER_LIST:
                         AvrcpControllerService.getPlayerListNative(
-                            mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
-                            (byte) msg.arg2);
+                                mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+                                (byte) msg.arg2);
                         transitionTo(mGetPlayerListing);
                         sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
                         break;
@@ -284,8 +272,8 @@
                         // 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));
+                                mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1,
+                                AvrcpControllerService.hexStringToByteUID(uid));
                         mChangeFolderPath.setFolder(fid);
                         transitionTo(mChangeFolderPath);
                         sendMessage(MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT, (byte) msg.arg1);
@@ -296,13 +284,12 @@
                     case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM: {
                         int scope = msg.arg1;
                         String playItemUid = (String) msg.obj;
-                        BrowseTree.BrowseNode currBrPlayer =
-                            mBrowseTree.getCurrentBrowsedPlayer();
+                        BrowseTree.BrowseNode currBrPlayer = mBrowseTree.getCurrentBrowsedPlayer();
                         BrowseTree.BrowseNode currAddrPlayer =
-                            mBrowseTree.getCurrentAddressedPlayer();
+                                mBrowseTree.getCurrentAddressedPlayer();
                         if (DBG) {
-                            Log.d(TAG, "currBrPlayer " + currBrPlayer +
-                                " currAddrPlayer " + currAddrPlayer);
+                            Log.d(TAG, "currBrPlayer " + currBrPlayer + " currAddrPlayer "
+                                    + currAddrPlayer);
                         }
 
                         if (currBrPlayer == null || currBrPlayer.equals(currAddrPlayer)) {
@@ -311,15 +298,16 @@
                             // 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);
+                                    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);
+                                    mRemoteDevice.getBluetoothAddress(),
+                                    currBrPlayer.getPlayerID());
+                            mSetAddrPlayer.setItemAndScope(currBrPlayer.getID(), playItemUid,
+                                    scope);
                             transitionTo(mSetAddrPlayer);
                         }
                         break;
@@ -327,12 +315,20 @@
 
                     case MESSAGE_SET_BROWSED_PLAYER: {
                         AvrcpControllerService.setBrowsedPlayerNative(
-                            mRemoteDevice.getBluetoothAddress(), (int) msg.arg1);
+                                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) {
@@ -343,11 +339,11 @@
                             transitionTo(mDisconnected);
                             BluetoothDevice rtDevice = (BluetoothDevice) msg.obj;
                             Intent intent = new Intent(
-                                BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
+                                    BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
                             intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
-                                BluetoothProfile.STATE_CONNECTED);
+                                    BluetoothProfile.STATE_CONNECTED);
                             intent.putExtra(BluetoothProfile.EXTRA_STATE,
-                                BluetoothProfile.STATE_DISCONNECTED);
+                                    BluetoothProfile.STATE_DISCONNECTED);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice);
                             mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
                         }
@@ -359,17 +355,17 @@
                         // the connection state handling should be done via the message
                         // MESSAGE_PROCESS_CONNECTION_CHANGE.
                         Intent intent = new Intent(
-                            AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
+                                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);
+                            intent.putExtra(BluetoothProfile.EXTRA_STATE,
+                                    BluetoothProfile.STATE_CONNECTED);
                         } else if (msg.arg1 == 0) {
-                            intent.putExtra(
-                                BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
+                            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;
@@ -385,7 +381,10 @@
                         break;
 
                     case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
-                        mAbsoluteVolumeChangeInProgress = true;
+                        mVolumeChangedNotificationsToIgnore++;
+                        removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
+                        sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
+                                ABS_VOL_TIMEOUT_MILLIS);
                         setAbsVolume(msg.arg1, msg.arg2);
                         break;
 
@@ -393,28 +392,31 @@
                         mRemoteDevice.setNotificationLabel(msg.arg1);
                         mRemoteDevice.setAbsVolNotificationRequested(true);
                         int percentageVol = getVolumePercentage();
-                        Log.d(TAG,
-                            " Sending Interim Response = " + percentageVol + " label " + msg.arg1);
-                        AvrcpControllerService
-                            .sendRegisterAbsVolRspNative(mRemoteDevice.getBluetoothAddress(),
-                                NOTIFICATION_RSP_TYPE_INTERIM,
-                                percentageVol,
-                                mRemoteDevice.getNotificationLabel());
+                        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 (mAbsoluteVolumeChangeInProgress) {
-                            mAbsoluteVolumeChangeInProgress = false;
+                        if (mVolumeChangedNotificationsToIgnore > 0) {
+                            mVolumeChangedNotificationsToIgnore--;
+                            if (mVolumeChangedNotificationsToIgnore == 0) {
+                                removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
+                            }
                         } else {
                             if (mRemoteDevice.getAbsVolNotificationRequested()) {
                                 int percentageVol = getVolumePercentage();
-                                if (percentageVol != previousPercentageVol) {
+                                if (percentageVol != mPreviousPercentageVol) {
                                     AvrcpControllerService.sendRegisterAbsVolRspNative(
-                                        mRemoteDevice.getBluetoothAddress(),
-                                        NOTIFICATION_RSP_TYPE_CHANGED,
-                                        percentageVol, mRemoteDevice.getNotificationLabel());
-                                    previousPercentageVol = percentageVol;
+                                            mRemoteDevice.getBluetoothAddress(),
+                                            NOTIFICATION_RSP_TYPE_CHANGED, percentageVol,
+                                            mRemoteDevice.getNotificationLabel());
+                                    mPreviousPercentageVol = percentageVol;
                                     mRemoteDevice.setAbsVolNotificationRequested(false);
                                 }
                             }
@@ -422,17 +424,24 @@
                     }
                     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);
-                        if (mBroadcastMetadata) {
-                            broadcastMetaDataChanged(mAddressedPlayer.getCurrentTrack().
-                                getMediaMetaData());
-                        }
+                        broadcastMetaDataChanged(
+                                mAddressedPlayer.getCurrentTrack().getMediaMetaData());
                         break;
 
                     case MESSAGE_PROCESS_PLAY_POS_CHANGED:
-                        mAddressedPlayer.setPlayTime(msg.arg2);
-                        if (mBroadcastMetadata) {
+                        if (msg.arg2 != -1) {
+                            mAddressedPlayer.setPlayTime(msg.arg2);
                             broadcastPlayBackStateChanged(getCurrentPlayBackState());
                         }
                         break;
@@ -440,10 +449,11 @@
                     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) {
+                        } else if (status == PlaybackState.STATE_PAUSED
+                                || status == PlaybackState.STATE_STOPPED) {
                             a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, false);
                         }
                         break;
@@ -460,7 +470,7 @@
     // a) Send Change folder command
     // b) Once successful transition to folder fetch state.
     class ChangeFolderPath extends CmdState {
-        private String STATE_TAG = "AVRCPSM.ChangeFolderPath";
+        private static final String STATE_TAG = "AVRCPSM.ChangeFolderPath";
         private int mTmpIncrDirection;
         private String mID = "";
 
@@ -476,7 +486,7 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            Log.d(STATE_TAG, "processMessage " + msg);
+            if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
             switch (msg.what) {
                 case MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT:
                     mTmpIncrDirection = msg.arg1;
@@ -484,26 +494,28 @@
 
                 case MESSAGE_PROCESS_FOLDER_PATH: {
                     // Fetch the listing of objects in this folder.
-                    Log.d(STATE_TAG, "MESSAGE_PROCESS_FOLDER_PATH returned " + msg.arg1 +
-                        " elements");
+                    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) {
+                    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);
                     }
-                    Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
+                    if (DBG) Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
 
                     if (msg.arg1 > 0) {
-                        sendMessage(MESSAGE_GET_FOLDER_LIST, 0, msg.arg1 -1, mID);
+                        sendMessage(MESSAGE_GET_FOLDER_LIST, 0, msg.arg1 - 1, mID);
                     } else {
                         // Return an empty response to the upper layer.
-                        broadcastFolderList(mID, mEmptyMediaItemList);
+                        broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
                     }
                     mBrowseTree.setCurrentBrowsedFolder(mID);
                     transitionTo(mConnected);
@@ -514,12 +526,29 @@
                     // 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, mEmptyMediaItemList);
+                    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:
-                    Log.d(STATE_TAG, "deferring message " + msg + " to Connected state.");
+                    if (DBG) {
+                        Log.d(STATE_TAG, "deferring message " + msg.what + " to Connected state.");
+                    }
                     deferMessage(msg);
             }
             return true;
@@ -530,7 +559,7 @@
     // a) Fetch the listing of folders
     // b) Once completed return the object listing
     class GetFolderList extends CmdState {
-        private String STATE_TAG = "AVRCPSM.GetFolderList";
+        private static final String STATE_TAG = "AVRCPSM.GetFolderList";
 
         String mID = "";
         int mStartInd;
@@ -541,11 +570,12 @@
 
         @Override
         public void enter() {
+            // Setup the timeouts.
+            super.enter();
             mCurrInd = 0;
             mFolderList.clear();
-
-            callNativeFunctionForScope(
-                mStartInd, Math.min(mEndInd, mStartInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
+            callNativeFunctionForScope(mStartInd,
+                    Math.min(mEndInd, mStartInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
         }
 
         public void setScope(int scope) {
@@ -553,7 +583,7 @@
         }
 
         public void setFolder(String id) {
-            Log.d(STATE_TAG, "Setting folder to " + id);
+            if (DBG) Log.d(STATE_TAG, "Setting folder to " + id);
             mID = id;
         }
 
@@ -562,19 +592,20 @@
                 Log.d(STATE_TAG, "startInd " + startInd + " endInd " + endInd);
             }
             mStartInd = startInd;
-            mEndInd = endInd;
+            mEndInd = Math.min(endInd, MAX_FOLDER_ITEMS);
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            Log.d(STATE_TAG, "processMessage " + msg);
+            Log.d(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());
+                        Log.d(STATE_TAG,
+                                "Start " + mStartInd + " End " + mEndInd + " Curr " + mCurrInd
+                                        + " received " + folderList.size());
                     }
                     mCurrInd += folderList.size();
 
@@ -589,10 +620,8 @@
                         transitionTo(mConnected);
                     } else {
                         // Fetch the next set of items.
-                        callNativeFunctionForScope(
-                            (byte) mCurrInd,
-                            (byte) Math.min(
-                                mEndInd, mCurrInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
+                        callNativeFunctionForScope(mCurrInd, Math.min(mEndInd,
+                                mCurrInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
                         // 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);
@@ -613,8 +642,33 @@
                     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);
+                    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:
-                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
                     deferMessage(msg);
             }
             return true;
@@ -622,16 +676,18 @@
 
         private void sendFolderBroadcastAndUpdateNode() {
             BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID);
+            if (bn == null) {
+                Log.e(TAG, "Can not find BrowseNode by ID: " + mID);
+                return;
+            }
             if (bn.isPlayer()) {
                 // Add the now playing folder.
                 MediaDescription.Builder mdb = new MediaDescription.Builder();
-                mdb.setMediaId(BrowseTree.NOW_PLAYING_PREFIX + ":" +
-                    bn.getPlayerID());
+                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());
+                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));
             }
@@ -649,11 +705,11 @@
             switch (mScope) {
                 case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
                     AvrcpControllerService.getNowPlayingListNative(
-                        mRemoteDevice.getBluetoothAddress(), (byte) start, (byte) end);
+                            mRemoteDevice.getBluetoothAddress(), start, end);
                     break;
                 case AvrcpControllerService.BROWSE_SCOPE_VFS:
-                    AvrcpControllerService.getFolderListNative(
-                        mRemoteDevice.getBluetoothAddress(), (byte) start, (byte) end);
+                    AvrcpControllerService.getFolderListNative(mRemoteDevice.getBluetoothAddress(),
+                            start, end);
                     break;
                 default:
                     Log.e(STATE_TAG, "Scope " + mScope + " cannot be handled here.");
@@ -665,19 +721,18 @@
     // a) Fetch the listing of players
     // b) Once completed return the object listing
     class GetPlayerListing extends CmdState {
-        private String STATE_TAG = "AVRCPSM.GetPlayerList";
+        private static final String STATE_TAG = "AVRCPSM.GetPlayerList";
 
         @Override
         public boolean processMessage(Message msg) {
-            Log.d(STATE_TAG, "processMessage " + 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;
+                    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()) {
+                    for (BrowseTree.BrowseNode c : mBrowseTree.findBrowseNodeByID(BrowseTree.ROOT)
+                            .getChildren()) {
                         mediaItemList.add(c.getMediaItem());
                     }
                     broadcastFolderList(BrowseTree.ROOT, mediaItemList);
@@ -688,12 +743,27 @@
                 case MESSAGE_INTERNAL_CMD_TIMEOUT:
                     // We have timed out to execute the request.
                     // Send an empty list here.
-                    broadcastFolderList(BrowseTree.ROOT, mEmptyMediaItemList);
+                    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:
-                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
                     deferMessage(msg);
             }
             return true;
@@ -701,11 +771,11 @@
     }
 
     class MoveToRoot extends CmdState {
-        private String STATE_TAG = "AVRCPSM.MoveToRoot";
+        private static final String STATE_TAG = "AVRCPSM.MoveToRoot";
         private String mID = "";
 
         public void setFolder(String id) {
-            Log.d(STATE_TAG, "setFolder " + id);
+            if (DBG) Log.d(STATE_TAG, "setFolder " + id);
             mID = id;
         }
 
@@ -721,7 +791,9 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            Log.d(STATE_TAG, "processMessage " + msg + " browse depth " + mBrowseDepth);
+            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) {
@@ -730,15 +802,15 @@
                         sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID);
                     } else {
                         AvrcpControllerService.changeFolderPathNative(
-                            mRemoteDevice.getBluetoothAddress(),
-                            (byte) AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
-                            AvrcpControllerService.hexStringToByteUID(null));
+                                mRemoteDevice.getBluetoothAddress(),
+                                (byte) AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
+                                AvrcpControllerService.hexStringToByteUID(null));
                     }
                     break;
 
                 case MESSAGE_PROCESS_FOLDER_PATH:
                     mBrowseDepth -= 1;
-                    Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
+                    if (DBG) Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
                     if (mBrowseDepth < 0) {
                         throw new IllegalArgumentException("Browse depth negative!");
                     }
@@ -746,8 +818,28 @@
                     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:
-                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
                     deferMessage(msg);
             }
             return true;
@@ -755,7 +847,7 @@
     }
 
     class SetBrowsedPlayer extends CmdState {
-        private String STATE_TAG = "AVRCPSM.SetBrowsedPlayer";
+        private static final String STATE_TAG = "AVRCPSM.SetBrowsedPlayer";
         String mID = "";
 
         public void setFolder(String id) {
@@ -764,17 +856,17 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            Log.d(STATE_TAG, "processMessage " + msg);
+            if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what);
             switch (msg.what) {
                 case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
                     // Set the new depth.
-                    Log.d(STATE_TAG, "player depth " + msg.arg2);
+                    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, mEmptyMediaItemList);
+                        broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST);
                         transitionTo(mConnected);
                     } else {
                         // Otherwise move to root and fetch the listing.
@@ -788,12 +880,27 @@
                     break;
 
                 case MESSAGE_INTERNAL_CMD_TIMEOUT:
-                    broadcastFolderList(mID, mEmptyMediaItemList);
+                    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:
-                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
                     deferMessage(msg);
             }
             return true;
@@ -801,7 +908,7 @@
     }
 
     class SetAddresedPlayerAndPlayItem extends CmdState {
-        private String STATE_TAG = "AVRCPSM.SetAddresedPlayerAndPlayItem";
+        private static final String STATE_TAG = "AVRCPSM.SetAddresedPlayerAndPlayItem";
         int mScope;
         String mPlayItemId;
         String mAddrPlayerId;
@@ -814,16 +921,16 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            Log.d(STATE_TAG, "processMessage " + 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);
+                    AvrcpControllerService.playItemNative(mRemoteDevice.getBluetoothAddress(),
+                            (byte) mScope, AvrcpControllerService.hexStringToByteUID(mPlayItemId),
+                            (int) 0);
 
                     // Transition to connected state here.
                     transitionTo(mConnected);
@@ -833,8 +940,23 @@
                     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:
-                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
+                    if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!");
                     deferMessage(msg);
             }
             return true;
@@ -888,7 +1010,7 @@
                     Log.d(TAG, "getCurrentMetaData mmd " + mmd);
                 }
             }
-            return mEmptyMMD;
+            return EMPTY_MEDIA_METADATA;
         }
     }
 
@@ -901,7 +1023,7 @@
             synchronized (mLock) {
                 if (mAddressedPlayer == null) {
                     return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
-                        PlaybackState.PLAYBACK_POSITION_UNKNOWN,0).build();
+                            PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0).build();
                 }
                 return mAddressedPlayer.getPlaybackState();
             }
@@ -924,13 +1046,13 @@
         BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(parentMediaId);
         if (bn == null) {
             Log.e(TAG, "Invalid folder to browse " + mBrowseTree);
-            broadcastFolderList(parentMediaId, mEmptyMediaItemList);
+            broadcastFolderList(parentMediaId, EMPTY_MEDIA_ITEM_LIST);
             return;
         }
 
         if (DBG) {
-            Log.d(TAG, "To Browse folder " + bn + " is cached " + bn.isCached() +
-                " current folder " + mBrowseTree.getCurrentBrowsedFolder());
+            Log.d(TAG, "To Browse folder " + bn + " is cached " + bn.isCached() + " current folder "
+                    + mBrowseTree.getCurrentBrowsedFolder());
         }
         if (bn.equals(mBrowseTree.getCurrentBrowsedFolder()) && bn.isCached()) {
             if (DBG) {
@@ -949,8 +1071,8 @@
         int btDirection = mBrowseTree.getDirection(parentMediaId);
         BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder();
         if (DBG) {
-            Log.d(TAG, "Browse direction parent " + mBrowseTree.getCurrentBrowsedFolder() +
-                " req " + parentMediaId + " direction " + btDirection);
+            Log.d(TAG, "Browse direction parent " + mBrowseTree.getCurrentBrowsedFolder() + " req "
+                    + parentMediaId + " direction " + btDirection);
         }
         if (BrowseTree.ROOT.equals(parentMediaId)) {
             // Root contains the list of players.
@@ -959,12 +1081,11 @@
             // 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());
+                    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);
+            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
@@ -976,15 +1097,15 @@
             // 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);
+                    currFol.isNowPlaying() && bn.getID().equals(BrowseTree.ROOT);
             if (!isNowPlayingToRoot) {
                 // Find the direction of traversal.
                 int direction = -1;
-                Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection);
+                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, mEmptyMediaItemList);
+                    Log.w(TAG, "parent " + bn + " is not a direct "
+                            + "successor or predeccessor of current folder " + currFol);
+                    broadcastFolderList(parentMediaId, EMPTY_MEDIA_ITEM_LIST);
                     return;
                 }
 
@@ -997,13 +1118,12 @@
                 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);
+                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());
+                msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start,
+                        items, bn.getFolderUID());
             }
         }
 
@@ -1015,14 +1135,13 @@
     public void fetchAttrAndPlayItem(String uid) {
         BrowseTree.BrowseNode currItem = mBrowseTree.findFolderByIDLocked(uid);
         BrowseTree.BrowseNode currFolder = mBrowseTree.getCurrentBrowsedFolder();
-        Log.d(TAG, "fetchAttrAndPlayItem mediaId=" + uid + " node=" + currItem);
+        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());
+            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);
         }
     }
@@ -1030,7 +1149,7 @@
     private void broadcastMetaDataChanged(MediaMetadata metadata) {
         Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
         intent.putExtra(AvrcpControllerService.EXTRA_METADATA, metadata);
-        if (DBG) {
+        if (VDBG) {
             Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription());
         }
         mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
@@ -1038,10 +1157,9 @@
 
     private void broadcastFolderList(String id, ArrayList<MediaItem> items) {
         Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST);
-        Log.d(TAG, "broadcastFolderList id " + id + " items " + items);
+        if (VDBG) Log.d(TAG, "broadcastFolderList id " + id + " items " + items);
         intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id);
-        intent.putParcelableArrayListExtra(
-            AvrcpControllerService.EXTRA_FOLDER_LIST, items);
+        intent.putParcelableArrayListExtra(AvrcpControllerService.EXTRA_FOLDER_LIST, items);
         mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
     }
 
@@ -1061,9 +1179,10 @@
         // and amplifier volume.
         if (mRemoteDevice.getFirstAbsVolCmdRecvd()) {
             int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
-            Log.d(TAG,
-                " setAbsVolume =" + absVol + " maxVol = " + maxVolume + " cur = " + currIndex +
-                    " new = " + newIndex);
+            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
@@ -1071,15 +1190,15 @@
              */
             if (newIndex != currIndex) {
                 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
-                    AudioManager.FLAG_SHOW_UI);
+                        AudioManager.FLAG_SHOW_UI);
             }
         } else {
             mRemoteDevice.setFirstAbsVolCmdRecvd();
             absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
-            Log.d(TAG, " SetAbsVol recvd for first time, respond with " + absVol);
+            if (DBG) Log.d(TAG, " SetAbsVol recvd for first time, respond with " + absVol);
         }
-        AvrcpControllerService.sendAbsVolRspNative(
-            mRemoteDevice.getBluetoothAddress(), absVol, label);
+        AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol,
+                label);
     }
 
     private int getVolumePercentage() {
@@ -1140,28 +1259,30 @@
     }
 
     public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) {
-        StringBuffer sb =  new StringBuffer();
+        StringBuffer sb = new StringBuffer();
         int supportedSetting = mSett.getSettings();
-        if(VDBG) Log.d(TAG," setting: " + supportedSetting);
-        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
+        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)));
+            sb.append(Integer.toString(mSett.getSettingValue(
+                    BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER)));
         }
-        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
+        if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
             sb.append(" REPEAT : ");
-            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
-                                                             SETTING_REPEAT)));
+            sb.append(Integer.toString(mSett.getSettingValue(
+                    BluetoothAvrcpPlayerSettings.SETTING_REPEAT)));
         }
-        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
+        if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
             sb.append(" SHUFFLE : ");
-            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
-                                                             SETTING_SHUFFLE)));
+            sb.append(Integer.toString(mSett.getSettingValue(
+                    BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE)));
         }
-        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
+        if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
             sb.append(" SCAN : ");
-            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
-                                                             SETTING_SCAN)));
+            sb.append(Integer.toString(mSett.getSettingValue(
+                    BluetoothAvrcpPlayerSettings.SETTING_SCAN)));
         }
         return sb.toString();
     }
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index 3f037c2..d9e4924 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -16,12 +16,9 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
 import android.media.session.PlaybackState;
 import android.util.Log;
 
-import java.util.ArrayList;
-
 /*
  * Contains information about remote player
  */
@@ -32,7 +29,7 @@
     public static final int INVALID_ID = -1;
 
     private int mPlayStatus = PlaybackState.STATE_NONE;
-    private long mPlayTime   = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+    private long mPlayTime = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
     private int mId;
     private String mName = "";
     private int mPlayerType;
@@ -53,7 +50,7 @@
     }
 
     public String getName() {
-      return mName;
+        return mName;
     }
 
     public void setPlayTime(int playTime) {
@@ -93,11 +90,11 @@
         return new PlaybackState.Builder().setState(mPlayStatus, position, speed).build();
     }
 
-    synchronized public void updateCurrentTrack(TrackInfo update) {
+    public synchronized void updateCurrentTrack(TrackInfo update) {
         mCurrentTrack = update;
     }
 
-    synchronized public TrackInfo getCurrentTrack() {
+    public synchronized TrackInfo getCurrentTrack() {
         return mCurrentTrack;
     }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 4c26dff..4482bd7 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -16,18 +16,16 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
+import android.media.MediaDescription;
 import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowser.MediaItem;
-import android.media.MediaDescription;
 import android.os.Bundle;
-import android.os.ResultReceiver;
 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.Stack;
 
 // Browsing hierarchy.
 // Root:
@@ -42,7 +40,8 @@
 //      ....
 public class BrowseTree {
     private static final String TAG = "BrowseTree";
-    private static final boolean DBG = true;
+    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;
@@ -152,8 +151,9 @@
         // 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 mItem.getDescription()
+                    .getExtras()
+                    .getString(AvrcpControllerService.MEDIA_ITEM_UID_KEY);
         }
 
         synchronized MediaItem getMediaItem() {
@@ -179,7 +179,11 @@
 
         @Override
         public String toString() {
-            return "ID: " + getID() + " desc: " + mItem;
+            if (VDBG) {
+                return "ID: " + getID() + " desc: " + mItem;
+            } else {
+                return "ID: " + getID();
+            }
         }
     }
 
@@ -209,7 +213,7 @@
 
         String parentID = parent.getID();
         // Make sure that the child list is clean.
-        if (DBG) {
+        if (VDBG) {
             Log.d(TAG, "parent " + parentID + " child list " + parent.getChildren());
         }
 
@@ -228,7 +232,7 @@
             Log.e(TAG, "folder " + parentID + " not found!");
             return null;
         }
-        if (DBG) {
+        if (VDBG) {
             Log.d(TAG, "Browse map: " + mBrowseMap);
         }
         return bn;
@@ -266,9 +270,7 @@
         } else if (fromFolder.equals(toFolder)) {
             return DIRECTION_SAME;
         } else {
-            Log.w(TAG, "from folder " + mCurrentBrowseNode + " children " +
-                fromFolder.getChildren() + "to folder " + toUID + " children " +
-                toFolder.getChildren());
+            Log.w(TAG, "from folder " + mCurrentBrowseNode + "to folder " + toUID);
             return DIRECTION_UNKNOWN;
         }
     }
diff --git a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
index 65d9966..c34a2d7 100644
--- a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
+++ b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java
@@ -17,7 +17,6 @@
 package com.android.bluetooth.avrcpcontroller;
 
 import android.bluetooth.BluetoothAvrcpPlayerSettings;
-
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -67,7 +66,7 @@
      * in mSettings.
      */
     private Map<Integer, ArrayList<Integer>> mSupportedValues =
-        new HashMap<Integer, ArrayList<Integer>>();
+            new HashMap<Integer, ArrayList<Integer>>();
 
     /* Convert from JNI array to Java classes. */
     static PlayerApplicationSettings makeSupportedSettings(byte[] btAvrcpAttributeList) {
@@ -80,14 +79,14 @@
 
                 for (int j = 0; j < numSupportedVals; j++) {
                     // Yes, keep using i for array indexing.
-                    supportedValues.add(mapAttribIdValtoAvrcpPlayerSetting(attrId,
-                        btAvrcpAttributeList[i++]));
+                    supportedValues.add(
+                            mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++]));
                 }
                 newObj.mSupportedValues.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
-                    supportedValues);
+                        supportedValues);
             }
         } catch (ArrayIndexOutOfBoundsException exception) {
-            Log.e(TAG,"makeSupportedSettings attributeList index error.");
+            Log.e(TAG, "makeSupportedSettings attributeList index error.");
         }
         return newObj;
     }
@@ -111,11 +110,10 @@
                 byte attrId = btAvrcpAttributeList[i++];
 
                 newObj.mSettings.put(mapBTAttribIdToAvrcpPlayerSettings(attrId),
-                    mapAttribIdValtoAvrcpPlayerSetting(attrId,
-                        btAvrcpAttributeList[i++]));
+                        mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++]));
             }
         } catch (ArrayIndexOutOfBoundsException exception) {
-            Log.e(TAG,"makeSettings JNI_ATTRIButeList index error.");
+            Log.e(TAG, "makeSettings JNI_ATTRIButeList index error.");
         }
         return newObj;
     }
@@ -147,18 +145,18 @@
         try {
             if ((supportedSettings & settingSubset) == settingSubset) {
                 for (Integer settingId : mSettings.keySet()) {
-                    if ((settingId & settingSubset )== settingId &&
-                        (!mSupportedValues.get(settingId).contains(settingsToCheck.
-                            getSettingValue(settingId))))
-                        // The setting is in both settings to check and supported settings but the
-                        // value is not supported.
+                    // 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.");
+                    "supportsSettings received a supported setting that has no supported values.");
         }
         return false;
     }
@@ -169,30 +167,29 @@
         int i = 0;
         ArrayList<Byte> attribArray = new ArrayList<Byte>();
         for (Integer settingId : mSettings.keySet()) {
-            switch (settingId)
-            {
+            switch (settingId) {
                 case BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER:
                     attribArray.add(JNI_ATTRIB_EQUALIZER_STATUS);
-                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(
-                        settingId, mSettings.get(settingId)));
+                    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)));
+                    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)));
+                    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)));
+                    attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId,
+                            mSettings.get(settingId)));
                     break;
                 default:
-                    Log.w(TAG,"Unknown setting found in getNativeSettings: " + settingId);
+                    Log.w(TAG, "Unknown setting found in getNativeSettings: " + settingId);
             }
         }
         return attribArray;
@@ -201,14 +198,14 @@
     // 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) {
+            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) {
-            switch(attribVal) {
+            switch (attribVal) {
                 case JNI_REPEAT_STATUS_ALL_TRACK_REPEAT:
                     return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
                 case JNI_REPEAT_STATUS_GROUP_REPEAT:
@@ -219,7 +216,7 @@
                     return BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK;
             }
         } else if (attribId == JNI_ATTRIB_SCAN_STATUS) {
-            switch(attribVal) {
+            switch (attribVal) {
                 case JNI_SCAN_STATUS_ALL_TRACK_SCAN:
                     return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
                 case JNI_SCAN_STATUS_GROUP_SCAN:
@@ -228,7 +225,7 @@
                     return BluetoothAvrcpPlayerSettings.STATE_OFF;
             }
         } else if (attribId == JNI_ATTRIB_SHUFFLE_STATUS) {
-            switch(attribVal) {
+            switch (attribVal) {
                 case JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE:
                     return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK;
                 case JNI_SHUFFLE_STATUS_GROUP_SHUFFLE:
@@ -243,14 +240,14 @@
     // 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) {
+            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) {
+            switch (mSettingVal) {
                 case BluetoothAvrcpPlayerSettings.STATE_OFF:
                     return JNI_REPEAT_STATUS_OFF;
                 case BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK:
@@ -261,7 +258,7 @@
                     return JNI_REPEAT_STATUS_GROUP_REPEAT;
             }
         } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) {
-            switch(mSettingVal) {
+            switch (mSettingVal) {
                 case BluetoothAvrcpPlayerSettings.STATE_OFF:
                     return JNI_SHUFFLE_STATUS_OFF;
                 case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
@@ -270,7 +267,7 @@
                     return JNI_SHUFFLE_STATUS_GROUP_SHUFFLE;
             }
         } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SCAN) {
-            switch(mSettingVal) {
+            switch (mSettingVal) {
                 case BluetoothAvrcpPlayerSettings.STATE_OFF:
                     return JNI_SCAN_STATUS_OFF;
                 case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK:
@@ -284,7 +281,7 @@
 
     // convert a native Attribute Id into the AVRCP Setting equivalent value;
     private static int mapBTAttribIdToAvrcpPlayerSettings(byte attribId) {
-        switch(attribId) {
+        switch (attribId) {
             case JNI_ATTRIB_EQUALIZER_STATUS:
                 return BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER;
             case JNI_ATTRIB_REPEAT_STATUS:
diff --git a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
index e535f92..7946747 100644
--- a/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
+++ b/src/com/android/bluetooth/avrcpcontroller/RemoteDevice.java
@@ -16,15 +16,10 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
-import android.bluetooth.BluetoothAvrcpPlayerSettings;
 import android.bluetooth.BluetoothDevice;
-import android.media.session.PlaybackState;
 
 import com.android.bluetooth.Utils;
 
-import java.util.ArrayList;
-import java.nio.ByteBuffer;
-
 /*
  * Contains information about remote device specifically the player and features enabled on it along
  * with an encapsulation of the current track and playlist information.
@@ -59,31 +54,31 @@
         mRemoteFeatures = remoteFeatures;
     }
 
-    synchronized public byte[] getBluetoothAddress() {
+    public synchronized byte[] getBluetoothAddress() {
         return Utils.getByteAddress(mBTDevice);
     }
 
-    synchronized public void setNotificationLabel(int label) {
+    public synchronized void setNotificationLabel(int label) {
         mNotificationLabel = label;
     }
 
-    synchronized public int getNotificationLabel() {
+    public synchronized int getNotificationLabel() {
         return mNotificationLabel;
     }
 
-    synchronized public void setAbsVolNotificationRequested(boolean request) {
+    public synchronized void setAbsVolNotificationRequested(boolean request) {
         mAbsVolNotificationRequested = request;
     }
 
-    synchronized public boolean getAbsVolNotificationRequested() {
+    public synchronized boolean getAbsVolNotificationRequested() {
         return mAbsVolNotificationRequested;
     }
 
-    synchronized public void setFirstAbsVolCmdRecvd() {
+    public synchronized void setFirstAbsVolCmdRecvd() {
         mFirstAbsVolCmdRecvd = true;
     }
 
-    synchronized public boolean getFirstAbsVolCmdRecvd() {
+    public synchronized boolean getFirstAbsVolCmdRecvd() {
         return mFirstAbsVolCmdRecvd;
     }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
index 033ea04..e89fb4c 100644
--- a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -21,9 +21,9 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.HashMap;
 
 /*
  * Contains information about tracks that either currently playing or maintained in playlist
@@ -32,7 +32,7 @@
  */
 class TrackInfo {
     private static final String TAG = "AvrcpTrackInfo";
-    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
 
     /*
      * Default values for each of the items from JNI
@@ -59,14 +59,14 @@
     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.
+    private final long mTotalTracks; // total number of tracks on original recording
+    private final long mTrackLen; // full length of AudioFile.
 
-    public TrackInfo() {
+    TrackInfo() {
         this(new ArrayList<Integer>(), new ArrayList<String>());
     }
 
-    public TrackInfo(List<Integer> attrIds, List<String> attrMap) {
+    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));
@@ -80,41 +80,40 @@
         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;
+        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;
+        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;
+        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) + "]";
+        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 (DBG) Log.d(TAG, " TrackInfo " + toString());
+        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);
+        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();
     }
 
@@ -124,18 +123,25 @@
         StringBuffer sb = new StringBuffer();
         /* getDescription only contains artist, title and album */
         sb.append(metaData.getDescription().toString() + " ");
-        if(metaData.containsKey(MediaMetadata.METADATA_KEY_GENRE))
+        if (metaData.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
             sb.append(metaData.getString(MediaMetadata.METADATA_KEY_GENRE) + " ");
-        if(metaData.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID))
+        }
+        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))
+        }
+        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))
+        }
+        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))
+        }
+        if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
             sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+        }
         return sb.toString();
     }
 }
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
index 8cec40d..6a2e636 100644
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -20,7 +20,7 @@
  * @hide
  */
 
-final public class AbstractionLayer {
+public final class AbstractionLayer {
     // Do not modify without upating the HAL files.
 
     // TODO: Some of the constants are repeated from BluetoothAdapter.java.
@@ -48,9 +48,9 @@
     static final int BT_PROPERTY_REMOTE_VERSION_INFO = 0x0C;
     static final int BT_PROPERTY_LOCAL_LE_FEATURES = 0x0D;
 
-    static final int BT_DEVICE_TYPE_BREDR = 0x01;
-    static final int BT_DEVICE_TYPE_BLE = 0x02;
-    static final int BT_DEVICE_TYPE_DUAL = 0x03;
+    public static final int BT_DEVICE_TYPE_BREDR = 0x01;
+    public static final int BT_DEVICE_TYPE_BLE = 0x02;
+    public static final int BT_DEVICE_TYPE_DUAL = 0x03;
 
     static final int BT_BOND_STATE_NONE = 0x00;
     static final int BT_BOND_STATE_BONDING = 0x01;
@@ -80,6 +80,6 @@
     public static final int BT_STATUS_UNHANDLED = 8;
     public static final int BT_STATUS_AUTH_FAILURE = 9;
     public static final int BT_STATUS_RMT_DEV_DOWN = 10;
-    public static final int BT_STATUS_AUTH_REJECTED =11;
+    public static final int BT_STATUS_AUTH_REJECTED = 11;
     public static final int BT_STATUS_AUTH_TIMEOUT = 12;
 }
diff --git a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
new file mode 100644
index 0000000..e1f999d
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -0,0 +1,508 @@
+/*
+ * 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.btservice;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.os.Handler;
+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 java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The active device manager is responsible for keeping track of the
+ * connected A2DP/HFP/AVRCP/HearingAid devices and select which device is
+ * active (for each profile).
+ *
+ * Current policy (subject to change):
+ * 1) If the maximum number of connected devices is one, the manager doesn't
+ *    do anything. Each profile is responsible for automatically selecting
+ *    the connected device as active. Only if the maximum number of connected
+ *    devices is more than one, the rules below will apply.
+ * 2) The selected A2DP active device is the one used for AVRCP as well.
+ * 3) The HFP active device might be different from the A2DP active device.
+ * 4) The Active Device Manager always listens for ACTION_ACTIVE_DEVICE_CHANGED
+ *    broadcasts for each profile:
+ *    - BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED for A2DP
+ *    - BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED for HFP
+ *    - BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED for HearingAid
+ *    If such broadcast is received (e.g., triggered indirectly by user
+ *    action on the UI), the device in the received broacast is marked
+ *    as the current active device for that profile.
+ * 5) If there is a HearingAid active device, then A2DP and HFP active devices
+ *    must be set to null (i.e., A2DP and HFP cannot have active devices).
+ *    The reason is because A2DP or HFP cannot be used together with HearingAid.
+ * 6) If there are no connected devices (e.g., during startup, or after all
+ *    devices have been disconnected, the active device per profile
+ *    (A2DP/HFP/HearingAid) is selected as follows:
+ * 6.1) The last connected HearingAid device is selected as active.
+ *      If there is an active A2DP or HFP device, those must be set to null.
+ * 6.2) The last connected A2DP or HFP device is selected as active.
+ *      However, if there is an active HearingAid device, then the
+ *      A2DP or HFP active device is not set (must remain null).
+ * 7) If the currently active device (per profile) is disconnected, the
+ *    Active Device Manager just marks that the profile has no active device,
+ *    but does not attempt to select a new one. Currently, the expectation is
+ *    that the user will explicitly select the new active device.
+ * 8) If there is already an active device, and the corresponding
+ *    ACTION_ACTIVE_DEVICE_CHANGED broadcast is received, the device
+ *    contained in the broadcast is marked as active. However, if
+ *    the contained device is null, the corresponding profile is marked
+ *    as having no active device.
+ * 9) If a wired audio device is connected, the audio output is switched
+ *    by the Audio Framework itself to that device. We detect this here,
+ *    and the active device for each profile (A2DP/HFP/HearingAid) is set
+ *    to null to reflect the output device state change. However, if the
+ *    wired audio device is disconnected, we don't do anything explicit
+ *    and apply the default behavior instead:
+ * 9.1) If the wired headset is still the selected output device (i.e. the
+ *      active device is set to null), the Phone itself will become the output
+ *      device (i.e., the active device will remain null). If music was
+ *      playing, it will stop.
+ * 9.2) If one of the Bluetooth devices is the selected active device
+ *      (e.g., by the user in the UI), disconnecting the wired audio device
+ *      will have no impact. E.g., music will continue streaming over the
+ *      active Bluetooth device.
+ */
+class ActiveDeviceManager {
+    private static final boolean DBG = true;
+    private static final String TAG = "BluetoothActiveDeviceManager";
+
+    // Message types for the handler
+    private static final int MESSAGE_ADAPTER_ACTION_STATE_CHANGED = 1;
+    private static final int MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED = 2;
+    private static final int MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED = 3;
+    private static final int MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED = 4;
+    private static final int MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED = 5;
+    private static final int MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED = 6;
+
+    private final AdapterService mAdapterService;
+    private final ServiceFactory mFactory;
+    private HandlerThread mHandlerThread = null;
+    private Handler mHandler = null;
+    private final AudioManager mAudioManager;
+    private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback;
+
+    private final List<BluetoothDevice> mA2dpConnectedDevices = new LinkedList<>();
+    private final List<BluetoothDevice> mHfpConnectedDevices = new LinkedList<>();
+    private BluetoothDevice mA2dpActiveDevice = null;
+    private BluetoothDevice mHfpActiveDevice = null;
+    private BluetoothDevice mHearingAidActiveDevice = null;
+
+    // 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 BluetoothAdapter.ACTION_STATE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_ADAPTER_ACTION_STATE_CHANGED,
+                                           intent).sendToTarget();
+                    break;
+                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED,
+                                           intent).sendToTarget();
+                    break;
+                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED,
+                                           intent).sendToTarget();
+                    break;
+                case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED,
+                                           intent).sendToTarget();
+                    break;
+                case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED,
+                        intent).sendToTarget();
+                    break;
+                case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED,
+                            intent).sendToTarget();
+                    break;
+                default:
+                    Log.e(TAG, "Received unexpected intent, action=" + action);
+                    break;
+            }
+        }
+    };
+
+    class ActiveDeviceManagerHandler extends Handler {
+        ActiveDeviceManagerHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_ADAPTER_ACTION_STATE_CHANGED: {
+                    Intent intent = (Intent) msg.obj;
+                    int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+                    if (DBG) {
+                        Log.d(TAG, "handleMessage(MESSAGE_ADAPTER_ACTION_STATE_CHANGED): newState="
+                                + newState);
+                    }
+                    if (newState == BluetoothAdapter.STATE_ON) {
+                        resetState();
+                    }
+                }
+                break;
+
+                case MESSAGE_A2DP_ACTION_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 (prevState == nextState) {
+                        // Nothing has changed
+                        break;
+                    }
+                    if (nextState == BluetoothProfile.STATE_CONNECTED) {
+                        // Device connected
+                        if (DBG) {
+                            Log.d(TAG,
+                                    "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): "
+                                    + "device " + device + " connected");
+                        }
+                        if (mA2dpConnectedDevices.contains(device)) {
+                            break;      // The device is already connected
+                        }
+                        mA2dpConnectedDevices.add(device);
+                        if (mHearingAidActiveDevice == null) {
+                            // New connected device: select it as active
+                            setA2dpActiveDevice(device);
+                            break;
+                        }
+                        break;
+                    }
+                    if (prevState == BluetoothProfile.STATE_CONNECTED) {
+                        // Device disconnected
+                        if (DBG) {
+                            Log.d(TAG,
+                                    "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): "
+                                    + "device " + device + " disconnected");
+                        }
+                        mA2dpConnectedDevices.remove(device);
+                        if (Objects.equals(mA2dpActiveDevice, device)) {
+                            setA2dpActiveDevice(null);
+                        }
+                    }
+                }
+                break;
+
+                case MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED: {
+                    Intent intent = (Intent) msg.obj;
+                    BluetoothDevice device =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (DBG) {
+                        Log.d(TAG, "handleMessage(MESSAGE_A2DP_ACTION_ACTIVE_DEVICE_CHANGED): "
+                                + "device= " + device);
+                    }
+                    if (device != null && !Objects.equals(mA2dpActiveDevice, device)) {
+                        setHearingAidActiveDevice(null);
+                    }
+                    // Just assign locally the new value
+                    mA2dpActiveDevice = device;
+                }
+                break;
+
+                case MESSAGE_HFP_ACTION_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 (prevState == nextState) {
+                        // Nothing has changed
+                        break;
+                    }
+                    if (nextState == BluetoothProfile.STATE_CONNECTED) {
+                        // Device connected
+                        if (DBG) {
+                            Log.d(TAG,
+                                    "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): "
+                                    + "device " + device + " connected");
+                        }
+                        if (mHfpConnectedDevices.contains(device)) {
+                            break;      // The device is already connected
+                        }
+                        mHfpConnectedDevices.add(device);
+                        if (mHearingAidActiveDevice == null) {
+                            // New connected device: select it as active
+                            setHfpActiveDevice(device);
+                            break;
+                        }
+                        break;
+                    }
+                    if (prevState == BluetoothProfile.STATE_CONNECTED) {
+                        // Device disconnected
+                        if (DBG) {
+                            Log.d(TAG,
+                                    "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): "
+                                    + "device " + device + " disconnected");
+                        }
+                        mHfpConnectedDevices.remove(device);
+                        if (Objects.equals(mHfpActiveDevice, device)) {
+                            setHfpActiveDevice(null);
+                        }
+                    }
+                }
+                break;
+
+                case MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED: {
+                    Intent intent = (Intent) msg.obj;
+                    BluetoothDevice device =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (DBG) {
+                        Log.d(TAG, "handleMessage(MESSAGE_HFP_ACTION_ACTIVE_DEVICE_CHANGED): "
+                                + "device= " + device);
+                    }
+                    if (device != null && !Objects.equals(mHfpActiveDevice, device)) {
+                        setHearingAidActiveDevice(null);
+                    }
+                    // Just assign locally the new value
+                    mHfpActiveDevice = device;
+                }
+                break;
+
+                case MESSAGE_HEARING_AID_ACTION_ACTIVE_DEVICE_CHANGED: {
+                    Intent intent = (Intent) msg.obj;
+                    BluetoothDevice device =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    if (DBG) {
+                        Log.d(TAG, "handleMessage(MESSAGE_HA_ACTION_ACTIVE_DEVICE_CHANGED): "
+                                + "device= " + device);
+                    }
+                    // Just assign locally the new value
+                    mHearingAidActiveDevice = device;
+                    if (device != null) {
+                        setA2dpActiveDevice(null);
+                        setHfpActiveDevice(null);
+                    }
+                }
+                break;
+            }
+        }
+    }
+
+    /** Notifications of audio device connection and disconnection events. */
+    private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback {
+        private boolean isWiredAudioHeadset(AudioDeviceInfo deviceInfo) {
+            switch (deviceInfo.getType()) {
+                case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+                case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+                case AudioDeviceInfo.TYPE_USB_HEADSET:
+                    return true;
+                default:
+                    break;
+            }
+            return false;
+        }
+
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            if (DBG) {
+                Log.d(TAG, "onAudioDevicesAdded");
+            }
+            boolean hasAddedWiredDevice = false;
+            for (AudioDeviceInfo deviceInfo : addedDevices) {
+                if (DBG) {
+                    Log.d(TAG, "Audio device added: " + deviceInfo.getProductName() + " type: "
+                            + deviceInfo.getType());
+                }
+                if (isWiredAudioHeadset(deviceInfo)) {
+                    hasAddedWiredDevice = true;
+                    break;
+                }
+            }
+            if (hasAddedWiredDevice) {
+                wiredAudioDeviceConnected();
+            }
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+        }
+    }
+
+    ActiveDeviceManager(AdapterService service, ServiceFactory factory) {
+        mAdapterService = service;
+        mFactory = factory;
+        mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
+        mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback();
+    }
+
+    void start() {
+        if (DBG) {
+            Log.d(TAG, "start()");
+        }
+
+        mHandlerThread = new HandlerThread("BluetoothActiveDeviceManager");
+        mHandlerThread.start();
+        mHandler = new ActiveDeviceManagerHandler(mHandlerThread.getLooper());
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        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);
+        filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+        mAdapterService.registerReceiver(mReceiver, filter);
+
+        mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);
+    }
+
+    void cleanup() {
+        if (DBG) {
+            Log.d(TAG, "cleanup()");
+        }
+
+        mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback);
+        mAdapterService.unregisterReceiver(mReceiver);
+        if (mHandlerThread != null) {
+            mHandlerThread.quit();
+            mHandlerThread = null;
+        }
+        resetState();
+    }
+
+    /**
+     * 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();
+    }
+
+    private void setA2dpActiveDevice(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "setA2dpActiveDevice(" + device + ")");
+        }
+        final A2dpService a2dpService = mFactory.getA2dpService();
+        if (a2dpService == null) {
+            return;
+        }
+        if (!a2dpService.setActiveDevice(device)) {
+            return;
+        }
+        mA2dpActiveDevice = device;
+    }
+
+    private void setHfpActiveDevice(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "setHfpActiveDevice(" + device + ")");
+        }
+        final HeadsetService headsetService = mFactory.getHeadsetService();
+        if (headsetService == null) {
+            return;
+        }
+        if (!headsetService.setActiveDevice(device)) {
+            return;
+        }
+        mHfpActiveDevice = device;
+    }
+
+    private void setHearingAidActiveDevice(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "setHearingAidActiveDevice(" + device + ")");
+        }
+        final HearingAidService hearingAidService = mFactory.getHearingAidService();
+        if (hearingAidService == null) {
+            return;
+        }
+        if (!hearingAidService.setActiveDevice(device)) {
+            return;
+        }
+        mHearingAidActiveDevice = device;
+    }
+
+    private void resetState() {
+        mA2dpConnectedDevices.clear();
+        mA2dpActiveDevice = null;
+
+        mHfpConnectedDevices.clear();
+        mHfpActiveDevice = null;
+
+        mHearingAidActiveDevice = null;
+    }
+
+    @VisibleForTesting
+    BroadcastReceiver getBroadcastReceiver() {
+        return mReceiver;
+    }
+
+    @VisibleForTesting
+    BluetoothDevice getA2dpActiveDevice() {
+        return mA2dpActiveDevice;
+    }
+
+    @VisibleForTesting
+    BluetoothDevice getHfpActiveDevice() {
+        return mHfpActiveDevice;
+    }
+
+    @VisibleForTesting
+    BluetoothDevice getHearingAidActiveDevice() {
+        return mHearingAidActiveDevice;
+    }
+
+    /**
+     * Called when a wired audio device is connected.
+     * It might be called multiple times each time a wired audio device is connected.
+     */
+    @VisibleForTesting
+    void wiredAudioDeviceConnected() {
+        if (DBG) {
+            Log.d(TAG, "wiredAudioDeviceConnected");
+        }
+        setA2dpActiveDevice(null);
+        setHfpActiveDevice(null);
+        setHearingAidActiveDevice(null);
+    }
+}
diff --git a/src/com/android/bluetooth/btservice/AdapterApp.java b/src/com/android/bluetooth/btservice/AdapterApp.java
index b7b988a..0c22c7c 100644
--- a/src/com/android/bluetooth/btservice/AdapterApp.java
+++ b/src/com/android/bluetooth/btservice/AdapterApp.java
@@ -27,10 +27,12 @@
     private static final String TAG = "BluetoothAdapterApp";
     private static final boolean DBG = false;
     //For Debugging only
-    private static int sRefCount=0;
+    private static int sRefCount = 0;
 
     static {
-        if (DBG) Log.d(TAG,"Loading JNI Library");
+        if (DBG) {
+            Log.d(TAG, "Loading JNI Library");
+        }
         System.loadLibrary("bluetooth_jni");
     }
 
@@ -39,7 +41,7 @@
         if (DBG) {
             synchronized (AdapterApp.class) {
                 sRefCount++;
-                Log.d(TAG, "REFCOUNT: Constructed "+ this + " Instance Count = " + sRefCount);
+                Log.d(TAG, "REFCOUNT: Constructed " + this + " Instance Count = " + sRefCount);
             }
         }
     }
@@ -47,7 +49,9 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        if (DBG) Log.d(TAG, "onCreate");
+        if (DBG) {
+            Log.d(TAG, "onCreate");
+        }
         Config.init(this);
     }
 
@@ -56,7 +60,7 @@
         if (DBG) {
             synchronized (AdapterApp.class) {
                 sRefCount--;
-                Log.d(TAG, "REFCOUNT: Finalized: " + this +", Instance Count = " + sRefCount);
+                Log.d(TAG, "REFCOUNT: Finalized: " + this + ", Instance Count = " + sRefCount);
             }
         }
     }
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index 07b7bfe..981a45a 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -20,11 +20,12 @@
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAvrcpController;
+import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothInputHost;
+import android.bluetooth.BluetoothHidDevice;
+import android.bluetooth.BluetoothHidHost;
 import android.bluetooth.BluetoothMap;
 import android.bluetooth.BluetoothMapClient;
 import android.bluetooth.BluetoothPan;
@@ -37,32 +38,47 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.ParcelUuid;
+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 com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
 
-import java.lang.System;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 class AdapterProperties {
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
-    private static final String TAG = "BluetoothAdapterProperties";
+    private static final String TAG = "AdapterProperties";
+
+    private static final String MAX_CONNECTED_AUDIO_DEVICES_PROPERTY =
+            "persist.bluetooth.maxconnectedaudiodevices";
+    static final int MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND = 1;
+    private static final int MAX_CONNECTED_AUDIO_DEVICES_UPPER_BOUND = 5;
+    private static final String A2DP_OFFLOAD_SUPPORTED_PROPERTY =
+            "ro.bluetooth.a2dp_offload.supported";
+    private static final String A2DP_OFFLOAD_DISABLED_PROPERTY =
+            "persist.bluetooth.a2dp_offload.disabled";
 
     private static final long DEFAULT_DISCOVERY_TIMEOUT_MS = 12800;
     private static final int BD_ADDR_LEN = 6; // in bytes
 
     private volatile String mName;
     private volatile byte[] mAddress;
-    private volatile int mBluetoothClass;
+    private volatile BluetoothClass mBluetoothClass;
     private volatile int mScanMode;
     private volatile int mDiscoverableTimeout;
     private volatile ParcelUuid[] mUuids;
-    private CopyOnWriteArrayList<BluetoothDevice> mBondedDevices = new CopyOnWriteArrayList<BluetoothDevice>();
+    private CopyOnWriteArrayList<BluetoothDevice> mBondedDevices =
+            new CopyOnWriteArrayList<BluetoothDevice>();
 
     private int mProfilesConnecting, mProfilesConnected, mProfilesDisconnecting;
     private final HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState =
@@ -70,6 +86,8 @@
 
     private volatile int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
     private volatile int mState = BluetoothAdapter.STATE_OFF;
+    private int mMaxConnectedAudioDevices = 1;
+    private boolean mA2dpOffloadEnabled = false;
 
     private AdapterService mService;
     private boolean mDiscovering;
@@ -115,11 +133,11 @@
                 case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED:
                     sendConnectionStateChange(BluetoothProfile.A2DP_SINK, intent);
                     break;
-                case BluetoothInputHost.ACTION_CONNECTION_STATE_CHANGED:
-                    sendConnectionStateChange(BluetoothProfile.INPUT_HOST, intent);
+                case BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED:
+                    sendConnectionStateChange(BluetoothProfile.HID_DEVICE, intent);
                     break;
-                case BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED:
-                    sendConnectionStateChange(BluetoothProfile.INPUT_DEVICE, intent);
+                case BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED:
+                    sendConnectionStateChange(BluetoothProfile.HID_HOST, intent);
                     break;
                 case BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED:
                     sendConnectionStateChange(BluetoothProfile.AVRCP_CONTROLLER, intent);
@@ -139,6 +157,9 @@
                 case BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED:
                     sendConnectionStateChange(BluetoothProfile.PBAP_CLIENT, intent);
                     break;
+                case BluetoothPbap.ACTION_CONNECTION_STATE_CHANGED:
+                    sendConnectionStateChange(BluetoothProfile.PBAP, intent);
+                    break;
                 default:
                     Log.w(TAG, "Received unknown intent " + intent);
                     break;
@@ -151,21 +172,41 @@
     // can be added here.
     private final Object mObject = new Object();
 
-    public AdapterProperties(AdapterService service) {
+    AdapterProperties(AdapterService service) {
         mService = service;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
     }
+
     public void init(RemoteDevices remoteDevices) {
         mProfileConnectionState.clear();
         mRemoteDevices = remoteDevices;
 
+        // Get default max connected audio devices from config.xml in frameworks/base/core
+        int configDefaultMaxConnectedAudioDevices = mService.getResources().getInteger(
+                com.android.internal.R.integer.config_bluetooth_max_connected_audio_devices);
+        // Override max connected audio devices if MAX_CONNECTED_AUDIO_DEVICES_PROPERTY is set
+        int propertyOverlayedMaxConnectedAudioDevices =
+                SystemProperties.getInt(MAX_CONNECTED_AUDIO_DEVICES_PROPERTY,
+                        configDefaultMaxConnectedAudioDevices);
+        // Make sure the final value of max connected audio devices is within allowed range
+        mMaxConnectedAudioDevices = Math.min(Math.max(propertyOverlayedMaxConnectedAudioDevices,
+                MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND), MAX_CONNECTED_AUDIO_DEVICES_UPPER_BOUND);
+        Log.i(TAG, "init(), maxConnectedAudioDevices, default="
+                + configDefaultMaxConnectedAudioDevices + ", propertyOverlayed="
+                + propertyOverlayedMaxConnectedAudioDevices + ", finalValue="
+                + mMaxConnectedAudioDevices);
+
+        mA2dpOffloadEnabled =
+                SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false)
+                && !SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, false);
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothInputHost.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
@@ -205,16 +246,45 @@
      */
     boolean setName(String name) {
         synchronized (mObject) {
-            return mService.setAdapterPropertyNative(
-                    AbstractionLayer.BT_PROPERTY_BDNAME, name.getBytes());
+            return mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME,
+                    name.getBytes());
         }
     }
 
     /**
-     * @return the mClass
+     * Set the Bluetooth Class of Device (CoD) of the adapter.
+     *
+     * <p>Bluetooth stack stores some adapter properties in native BT stack storage and some in the
+     * Java Android stack. Bluetooth CoD is stored in the Android layer through
+     * {@link android.provider.Settings.Global#BLUETOOTH_CLASS_OF_DEVICE}.
+     *
+     * <p>Due to this, the getAdapterPropertyNative and adapterPropertyChangedCallback methods don't
+     * actually update mBluetoothClass. Hence, we update the field mBluetoothClass every time we
+     * successfully update BluetoothClass.
+     *
+     * @param bluetoothClass BluetoothClass of the device
      */
-    int getBluetoothClass() {
-        return mBluetoothClass;
+    boolean setBluetoothClass(BluetoothClass bluetoothClass) {
+        synchronized (mObject) {
+            boolean result =
+                    mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE,
+                            bluetoothClass.getClassOfDeviceBytes());
+
+            if (result) {
+                mBluetoothClass = bluetoothClass;
+            }
+
+            return result;
+        }
+    }
+
+    /**
+     * @return the BluetoothClass of the Bluetooth adapter.
+     */
+    BluetoothClass getBluetoothClass() {
+        synchronized (mObject) {
+            return mBluetoothClass;
+        }
     }
 
     /**
@@ -231,8 +301,8 @@
      */
     boolean setScanMode(int scanMode) {
         synchronized (mObject) {
-            return mService.setAdapterPropertyNative(
-                    AbstractionLayer.BT_PROPERTY_ADAPTER_SCAN_MODE, Utils.intToByteArray(scanMode));
+            return mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_ADAPTER_SCAN_MODE,
+                    Utils.intToByteArray(scanMode));
         }
     }
 
@@ -251,10 +321,10 @@
     }
 
     /**
-     * @param mConnectionState the mConnectionState to set
+     * @param connectionState the mConnectionState to set
      */
-    void setConnectionState(int mConnectionState) {
-        this.mConnectionState = mConnectionState;
+    void setConnectionState(int connectionState) {
+        mConnectionState = connectionState;
     }
 
     /**
@@ -267,9 +337,9 @@
     /**
      * @param mState the mState to set
      */
-    void setState(int mState) {
-        debugLog("Setting state to " + mState);
-        this.mState = mState;
+    void setState(int state) {
+        debugLog("Setting state to " + BluetoothAdapter.nameForState(state));
+        mState = state;
     }
 
     /**
@@ -364,13 +434,27 @@
     }
 
     /**
+     * @return the maximum number of connected audio devices
+     */
+    int getMaxConnectedAudioDevices() {
+        return mMaxConnectedAudioDevices;
+    }
+
+    /**
+     * @return A2DP offload support
+     */
+    boolean isA2dpOffloadEnabled() {
+        return mA2dpOffloadEnabled;
+    }
+
+    /**
      * @return the mBondedDevices
      */
     BluetoothDevice[] getBondedDevices() {
         BluetoothDevice[] bondedDeviceList = new BluetoothDevice[0];
         try {
             bondedDeviceList = mBondedDevices.toArray(bondedDeviceList);
-        } catch(ArrayStoreException ee) {
+        } catch (ArrayStoreException ee) {
             errorLog("Error retrieving bonded device array");
         }
         infoLog("getBondedDevices: length=" + bondedDeviceList.length);
@@ -379,8 +463,8 @@
 
     // This function shall be invoked from BondStateMachine whenever the bond
     // state changes.
-    void onBondStateChanged(BluetoothDevice device, int state)
-    {
+    @VisibleForTesting
+    void onBondStateChanged(BluetoothDevice device, int state) {
         if (device == null) {
             Log.w(TAG, "onBondStateChanged, device is null");
             return;
@@ -388,25 +472,26 @@
         try {
             byte[] addrByte = Utils.getByteAddress(device);
             DeviceProperties prop = mRemoteDevices.getDeviceProperties(device);
-            if (prop == null)
+            if (prop == null) {
                 prop = mRemoteDevices.addDeviceProperties(addrByte);
+            }
             prop.setBondState(state);
 
             if (state == BluetoothDevice.BOND_BONDED) {
                 // add if not already in list
-                if(!mBondedDevices.contains(device)) {
-                    debugLog("Adding bonded device:" +  device);
+                if (!mBondedDevices.contains(device)) {
+                    debugLog("Adding bonded device:" + device);
                     mBondedDevices.add(device);
                 }
             } else if (state == BluetoothDevice.BOND_NONE) {
                 // remove device from list
-                if (mBondedDevices.remove(device))
-                    debugLog("Removing bonded device:" +  device);
-                else
+                if (mBondedDevices.remove(device)) {
+                    debugLog("Removing bonded device:" + device);
+                } else {
                     debugLog("Failed to remove device: " + device);
+                }
             }
-        }
-        catch(Exception ee) {
+        } catch (Exception ee) {
             Log.w(TAG, "onBondStateChanged: Exception ", ee);
         }
     }
@@ -426,7 +511,9 @@
     int getProfileConnectionState(int profile) {
         synchronized (mObject) {
             Pair<Integer, Integer> p = mProfileConnectionState.get(profile);
-            if (p != null) return p.first;
+            if (p != null) {
+                return p.first;
+            }
             return BluetoothProfile.STATE_DISCONNECTED;
         }
     }
@@ -446,6 +533,11 @@
         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);
         if (!isNormalStateTransition(prevState, state)) {
             Log.w(TAG,
                     "PROFILE_CONNECTION_STATE_CHANGE: unexpected transition for profile=" + profile
@@ -453,9 +545,9 @@
         }
         sendConnectionStateChange(device, profile, state, prevState);
     }
+
     void sendConnectionStateChange(BluetoothDevice device, int profile, int state, int prevState) {
-        if (!validateProfileConnectionState(state) ||
-                !validateProfileConnectionState(prevState)) {
+        if (!validateProfileConnectionState(state) || !validateProfileConnectionState(prevState)) {
             // Previously, an invalid state was broadcast anyway,
             // with the invalid state converted to -1 in the intent.
             // Better to log an error and not send an intent with
@@ -478,14 +570,11 @@
                 intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, newAdapterState);
                 intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, prevAdapterState);
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                Log.d(TAG,
-                        "ADAPTER_CONNECTION_STATE_CHANGE: " + device + ": " + prevAdapterState
-                                + " -> " + newAdapterState);
+                Log.d(TAG, "ADAPTER_CONNECTION_STATE_CHANGE: " + device + ": " + prevAdapterState
+                        + " -> " + newAdapterState);
                 if (!isNormalStateTransition(prevState, state)) {
-                    Log.w(TAG,
-                            "ADAPTER_CONNECTION_STATE_CHANGE: unexpected transition for profile="
-                                    + profile + ", device=" + device + ", " + prevState + " -> "
-                                    + state);
+                    Log.w(TAG, "ADAPTER_CONNECTION_STATE_CHANGE: unexpected transition for profile="
+                            + profile + ", device=" + device + ", " + prevState + " -> " + state);
                 }
                 mService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
             }
@@ -493,10 +582,10 @@
     }
 
     private boolean validateProfileConnectionState(int state) {
-        return (state == BluetoothProfile.STATE_DISCONNECTED ||
-                state == BluetoothProfile.STATE_CONNECTING ||
-                state == BluetoothProfile.STATE_CONNECTED ||
-                state == BluetoothProfile.STATE_DISCONNECTING);
+        return (state == BluetoothProfile.STATE_DISCONNECTED
+                || state == BluetoothProfile.STATE_CONNECTING
+                || state == BluetoothProfile.STATE_CONNECTED
+                || state == BluetoothProfile.STATE_DISCONNECTING);
     }
 
     private static int convertToAdapterState(int state) {
@@ -522,8 +611,8 @@
                 return nextState == BluetoothProfile.STATE_DISCONNECTING;
             case BluetoothProfile.STATE_DISCONNECTING:
             case BluetoothProfile.STATE_CONNECTING:
-                return (nextState == BluetoothProfile.STATE_DISCONNECTED)
-                        || (nextState == BluetoothProfile.STATE_CONNECTED);
+                return (nextState == BluetoothProfile.STATE_DISCONNECTED) || (nextState
+                        == BluetoothProfile.STATE_CONNECTED);
             default:
                 return false;
         }
@@ -532,24 +621,33 @@
     private boolean updateCountersAndCheckForConnectionStateChange(int state, int prevState) {
         switch (prevState) {
             case BluetoothProfile.STATE_CONNECTING:
-                if (mProfilesConnecting > 0)
+                if (mProfilesConnecting > 0) {
                     mProfilesConnecting--;
-                else
+                } else {
                     Log.e(TAG, "mProfilesConnecting " + mProfilesConnecting);
+                    throw new IllegalStateException(
+                            "Invalid state transition, " + prevState + " -> " + state);
+                }
                 break;
 
             case BluetoothProfile.STATE_CONNECTED:
-                if (mProfilesConnected > 0)
+                if (mProfilesConnected > 0) {
                     mProfilesConnected--;
-                else
+                } else {
                     Log.e(TAG, "mProfilesConnected " + mProfilesConnected);
+                    throw new IllegalStateException(
+                            "Invalid state transition, " + prevState + " -> " + state);
+                }
                 break;
 
             case BluetoothProfile.STATE_DISCONNECTING:
-                if (mProfilesDisconnecting > 0)
+                if (mProfilesDisconnecting > 0) {
                     mProfilesDisconnecting--;
-                else
+                } else {
                     Log.e(TAG, "mProfilesDisconnecting " + mProfilesDisconnecting);
+                    throw new IllegalStateException(
+                            "Invalid state transition, " + prevState + " -> " + state);
+                }
                 break;
         }
 
@@ -600,28 +698,27 @@
             numDev = stateNumDev.second;
 
             if (newState == currHashState) {
-                numDev ++;
-            } else if (newState == BluetoothProfile.STATE_CONNECTED ||
-                   (newState == BluetoothProfile.STATE_CONNECTING &&
-                    currHashState != BluetoothProfile.STATE_CONNECTED)) {
-                 numDev = 1;
+                numDev++;
+            } else if (newState == BluetoothProfile.STATE_CONNECTED || (
+                    newState == BluetoothProfile.STATE_CONNECTING
+                            && currHashState != BluetoothProfile.STATE_CONNECTED)) {
+                numDev = 1;
             } else if (numDev == 1 && oldState == currHashState) {
-                 update = true;
+                update = true;
             } else if (numDev > 1 && oldState == currHashState) {
-                 numDev --;
+                numDev--;
 
-                 if (currHashState == BluetoothProfile.STATE_CONNECTED ||
-                     currHashState == BluetoothProfile.STATE_CONNECTING) {
+                if (currHashState == BluetoothProfile.STATE_CONNECTED
+                        || currHashState == BluetoothProfile.STATE_CONNECTING) {
                     newHashState = currHashState;
-                 }
+                }
             } else {
-                 update = false;
+                update = false;
             }
         }
 
         if (update) {
-            mProfileConnectionState.put(profile, new Pair<Integer, Integer>(newHashState,
-                    numDev));
+            mProfileConnectionState.put(profile, new Pair<Integer, Integer>(newHashState, numDev));
         }
     }
 
@@ -640,8 +737,8 @@
                         intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
                         intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, mName);
                         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                        mService.sendBroadcastAsUser(
-                                intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
+                        mService.sendBroadcastAsUser(intent, UserHandle.ALL,
+                                AdapterService.BLUETOOTH_PERM);
                         debugLog("Name is: " + mName);
                         break;
                     case AbstractionLayer.BT_PROPERTY_BDADDR:
@@ -651,11 +748,19 @@
                         intent = new Intent(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED);
                         intent.putExtra(BluetoothAdapter.EXTRA_BLUETOOTH_ADDRESS, address);
                         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                        mService.sendBroadcastAsUser(
-                                intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
+                        mService.sendBroadcastAsUser(intent, UserHandle.ALL,
+                                AdapterService.BLUETOOTH_PERM);
                         break;
                     case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE:
-                        mBluetoothClass = Utils.byteArrayToInt(val, 0);
+                        if (val == null || val.length != 3) {
+                            debugLog("Invalid BT CoD value from stack.");
+                            return;
+                        }
+                        int bluetoothClass =
+                                ((int) val[0] << 16) + ((int) val[1] << 8) + (int) val[2];
+                        if (bluetoothClass != 0) {
+                            mBluetoothClass = new BluetoothClass(bluetoothClass);
+                        }
                         debugLog("BT Class:" + mBluetoothClass);
                         break;
                     case AbstractionLayer.BT_PROPERTY_ADAPTER_SCAN_MODE:
@@ -666,22 +771,18 @@
                         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                         mService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
                         debugLog("Scan Mode:" + mScanMode);
-                        if (mBluetoothDisabling) {
-                            mBluetoothDisabling=false;
-                            mService.startBluetoothDisable();
-                        }
                         break;
                     case AbstractionLayer.BT_PROPERTY_UUIDS:
                         mUuids = Utils.byteArrayToUuid(val);
                         break;
                     case AbstractionLayer.BT_PROPERTY_ADAPTER_BONDED_DEVICES:
-                        int number = val.length/BD_ADDR_LEN;
+                        int number = val.length / BD_ADDR_LEN;
                         byte[] addrByte = new byte[BD_ADDR_LEN];
                         for (int j = 0; j < number; j++) {
                             System.arraycopy(val, j * BD_ADDR_LEN, addrByte, 0, BD_ADDR_LEN);
                             onBondStateChanged(mAdapter.getRemoteDevice(
-                                               Utils.getAddressStringFromByte(addrByte)),
-                                               BluetoothDevice.BOND_BONDED);
+                                    Utils.getAddressStringFromByte(addrByte)),
+                                    BluetoothDevice.BOND_BONDED);
                         }
                         break;
                     case AbstractionLayer.BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT:
@@ -701,61 +802,43 @@
     }
 
     private void updateFeatureSupport(byte[] val) {
-        mVersSupported = ((0xFF & ((int)val[1])) << 8)
-                            + (0xFF & ((int)val[0]));
-        mNumOfAdvertisementInstancesSupported = (0xFF & ((int)val[3]));
-        mRpaOffloadSupported = ((0xFF & ((int)val[4]))!= 0);
-        mNumOfOffloadedIrkSupported =  (0xFF & ((int)val[5]));
-        mNumOfOffloadedScanFilterSupported = (0xFF & ((int)val[6]));
-        mIsActivityAndEnergyReporting = ((0xFF & ((int)val[7])) != 0);
-        mOffloadedScanResultStorageBytes = ((0xFF & ((int)val[9])) << 8)
-                            + (0xFF & ((int)val[8]));
-        mTotNumOfTrackableAdv = ((0xFF & ((int)val[11])) << 8)
-                            + (0xFF & ((int)val[10]));
-        mIsExtendedScanSupported = ((0xFF & ((int)val[12])) != 0);
-        mIsDebugLogSupported = ((0xFF & ((int)val[13])) != 0);
+        mVersSupported = ((0xFF & ((int) val[1])) << 8) + (0xFF & ((int) val[0]));
+        mNumOfAdvertisementInstancesSupported = (0xFF & ((int) val[3]));
+        mRpaOffloadSupported = ((0xFF & ((int) val[4])) != 0);
+        mNumOfOffloadedIrkSupported = (0xFF & ((int) val[5]));
+        mNumOfOffloadedScanFilterSupported = (0xFF & ((int) val[6]));
+        mIsActivityAndEnergyReporting = ((0xFF & ((int) val[7])) != 0);
+        mOffloadedScanResultStorageBytes = ((0xFF & ((int) val[9])) << 8) + (0xFF & ((int) val[8]));
+        mTotNumOfTrackableAdv = ((0xFF & ((int) val[11])) << 8) + (0xFF & ((int) val[10]));
+        mIsExtendedScanSupported = ((0xFF & ((int) val[12])) != 0);
+        mIsDebugLogSupported = ((0xFF & ((int) val[13])) != 0);
         mIsLe2MPhySupported = ((0xFF & ((int) val[14])) != 0);
         mIsLeCodedPhySupported = ((0xFF & ((int) val[15])) != 0);
         mIsLeExtendedAdvertisingSupported = ((0xFF & ((int) val[16])) != 0);
         mIsLePeriodicAdvertisingSupported = ((0xFF & ((int) val[17])) != 0);
-        mLeMaximumAdvertisingDataLength =   (0xFF & ((int)val[18]))
-                                         + ((0xFF & ((int)val[19])) << 8);
+        mLeMaximumAdvertisingDataLength =
+                (0xFF & ((int) val[18])) + ((0xFF & ((int) val[19])) << 8);
 
         Log.d(TAG, "BT_PROPERTY_LOCAL_LE_FEATURES: update from BT controller"
                 + " mNumOfAdvertisementInstancesSupported = "
-                + mNumOfAdvertisementInstancesSupported
-                + " mRpaOffloadSupported = " + mRpaOffloadSupported
-                + " mNumOfOffloadedIrkSupported = "
-                + mNumOfOffloadedIrkSupported
-                + " mNumOfOffloadedScanFilterSupported = "
-                + mNumOfOffloadedScanFilterSupported
-                + " mOffloadedScanResultStorageBytes= "
-                + mOffloadedScanResultStorageBytes
-                + " mIsActivityAndEnergyReporting = "
-                + mIsActivityAndEnergyReporting
-                +" mVersSupported = "
-                + mVersSupported
-                + " mTotNumOfTrackableAdv = "
-                + mTotNumOfTrackableAdv
-                + " mIsExtendedScanSupported = "
-                + mIsExtendedScanSupported
-                + " mIsDebugLogSupported = "
-                + mIsDebugLogSupported
-                + " mIsLe2MPhySupported = "
-                + mIsLe2MPhySupported
-                + " mIsLeCodedPhySupported = "
-                + mIsLeCodedPhySupported
-                + " mIsLeExtendedAdvertisingSupported = "
-                + mIsLeExtendedAdvertisingSupported
-                + " mIsLePeriodicAdvertisingSupported = "
-                + mIsLePeriodicAdvertisingSupported
-                + " mLeMaximumAdvertisingDataLength = "
-                + mLeMaximumAdvertisingDataLength
-                );
+                + mNumOfAdvertisementInstancesSupported + " mRpaOffloadSupported = "
+                + mRpaOffloadSupported + " mNumOfOffloadedIrkSupported = "
+                + mNumOfOffloadedIrkSupported + " mNumOfOffloadedScanFilterSupported = "
+                + mNumOfOffloadedScanFilterSupported + " mOffloadedScanResultStorageBytes= "
+                + mOffloadedScanResultStorageBytes + " mIsActivityAndEnergyReporting = "
+                + mIsActivityAndEnergyReporting + " mVersSupported = " + mVersSupported
+                + " mTotNumOfTrackableAdv = " + mTotNumOfTrackableAdv
+                + " mIsExtendedScanSupported = " + mIsExtendedScanSupported
+                + " mIsDebugLogSupported = " + mIsDebugLogSupported + " mIsLe2MPhySupported = "
+                + mIsLe2MPhySupported + " mIsLeCodedPhySupported = " + mIsLeCodedPhySupported
+                + " mIsLeExtendedAdvertisingSupported = " + mIsLeExtendedAdvertisingSupported
+                + " mIsLePeriodicAdvertisingSupported = " + mIsLePeriodicAdvertisingSupported
+                + " mLeMaximumAdvertisingDataLength = " + mLeMaximumAdvertisingDataLength);
     }
 
     void onBluetoothReady() {
-        debugLog("onBluetoothReady, state=" + getState() + ", ScanMode=" + mScanMode);
+        debugLog("onBluetoothReady, state=" + BluetoothAdapter.nameForState(getState())
+                + ", ScanMode=" + mScanMode);
 
         synchronized (mObject) {
             // Reset adapter and profile connection states
@@ -764,49 +847,27 @@
             mProfilesConnected = 0;
             mProfilesConnecting = 0;
             mProfilesDisconnecting = 0;
-            // When BT is being turned on, all adapter properties will be sent in 1
-            // callback. At this stage, set the scan mode.
-            if (getState() == BluetoothAdapter.STATE_TURNING_ON &&
-                    mScanMode == BluetoothAdapter.SCAN_MODE_NONE) {
-                    /* mDiscoverableTimeout is part of the
-                       adapterPropertyChangedCallback received before
-                       onBluetoothReady */
-                    if (mDiscoverableTimeout != 0)
-                      setScanMode(AbstractionLayer.BT_SCAN_MODE_CONNECTABLE);
-                    else /* if timeout == never (0) at startup */
-                      setScanMode(AbstractionLayer.BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
-                    /* though not always required, this keeps NV up-to date on first-boot after flash */
-                    setDiscoverableTimeout(mDiscoverableTimeout);
-            }
+            // adapterPropertyChangedCallback has already been received.  Set the scan mode.
+            setScanMode(AbstractionLayer.BT_SCAN_MODE_CONNECTABLE);
+            // This keeps NV up-to date on first-boot after flash.
+            setDiscoverableTimeout(mDiscoverableTimeout);
         }
     }
 
-    private boolean mBluetoothDisabling = false;
-
     void onBleDisable() {
         // Sequence BLE_ON to STATE_OFF - that is _complete_ OFF state.
-        // When BT disable is invoked, set the scan_mode to NONE
-        // so no incoming connections are possible
         debugLog("onBleDisable");
-        if (getState() == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
-           setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
-        }
+        // Set the scan_mode to NONE (no incoming connections).
+        setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
     }
 
     void onBluetoothDisable() {
         // From STATE_ON to BLE_ON
-        // When BT disable is invoked, set the scan_mode to NONE
-        // so no incoming connections are possible
-
-        //Set flag to indicate we are disabling. When property change of scan mode done
-        //continue with disable sequence
         debugLog("onBluetoothDisable()");
-        mBluetoothDisabling = true;
-        if (getState() == BluetoothAdapter.STATE_TURNING_OFF) {
-            // Turn off any Device Search/Inquiry
-            mService.cancelDiscovery();
-            setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
-        }
+        // Turn off any Device Search/Inquiry
+        mService.cancelDiscovery();
+        // Set the scan_mode to NONE (no incoming connections).
+        setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
     }
 
     void discoveryStateChangeCallback(int state) {
@@ -827,12 +888,80 @@
         }
     }
 
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.println(TAG);
+        writer.println("  " + "Name: " + getName());
+        writer.println("  " + "Address: " + Utils.getAddressStringFromByte(mAddress));
+        writer.println("  " + "BluetoothClass: " + getBluetoothClass());
+        writer.println("  " + "ScanMode: " + dumpScanMode(getScanMode()));
+        writer.println("  " + "ConnectionState: " + dumpConnectionState(getConnectionState()));
+        writer.println("  " + "State: " + BluetoothAdapter.nameForState(getState()));
+        writer.println("  " + "MaxConnectedAudioDevices: " + getMaxConnectedAudioDevices());
+        writer.println("  " + "A2dpOffloadEnabled: " + mA2dpOffloadEnabled);
+        writer.println("  " + "Discovering: " + mDiscovering);
+        writer.println("  " + "DiscoveryEndMs: " + mDiscoveryEndMs);
+
+        writer.println("  " + "Bonded devices:");
+        for (BluetoothDevice device : mBondedDevices) {
+            writer.println(
+                    "    " + device.getAddress() + " [" + dumpDeviceType(device.getType()) + "] "
+                            + device.getName());
+        }
+    }
+
+    private String dumpDeviceType(int deviceType) {
+        switch (deviceType) {
+            case BluetoothDevice.DEVICE_TYPE_UNKNOWN:
+                return " ???? ";
+            case BluetoothDevice.DEVICE_TYPE_CLASSIC:
+                return "BR/EDR";
+            case BluetoothDevice.DEVICE_TYPE_LE:
+                return "  LE  ";
+            case BluetoothDevice.DEVICE_TYPE_DUAL:
+                return " DUAL ";
+            default:
+                return "Invalid device type: " + deviceType;
+        }
+    }
+
+    private String dumpConnectionState(int state) {
+        switch (state) {
+            case BluetoothAdapter.STATE_DISCONNECTED:
+                return "STATE_DISCONNECTED";
+            case BluetoothAdapter.STATE_DISCONNECTING:
+                return "STATE_DISCONNECTING";
+            case BluetoothAdapter.STATE_CONNECTING:
+                return "STATE_CONNECTING";
+            case BluetoothAdapter.STATE_CONNECTED:
+                return "STATE_CONNECTED";
+            default:
+                return "Unknown Connection State " + state;
+        }
+    }
+
+    private String dumpScanMode(int scanMode) {
+        switch (scanMode) {
+            case BluetoothAdapter.SCAN_MODE_NONE:
+                return "SCAN_MODE_NONE";
+            case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
+                return "SCAN_MODE_CONNECTABLE";
+            case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
+                return "SCAN_MODE_CONNECTABLE_DISCOVERABLE";
+            default:
+                return "Unknown Scan Mode " + scanMode;
+        }
+    }
+
     private static void infoLog(String msg) {
-        if (VDBG) Log.i(TAG, msg);
+        if (VDBG) {
+            Log.i(TAG, msg);
+        }
     }
 
     private static void debugLog(String msg) {
-        if (DBG) Log.d(TAG, msg);
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
     }
 
     private static void errorLog(String msg) {
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 8da1baf..c0772dd 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -16,16 +16,18 @@
 
 package com.android.bluetooth.btservice;
 
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.Service;
+import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothCallback;
-import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.IBluetoothSocketManager;
 import android.bluetooth.OobData;
 import android.bluetooth.UidTraffic;
 import android.content.BroadcastReceiver;
@@ -40,8 +42,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
-import android.os.ParcelFileDescriptor;
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.Process;
@@ -50,53 +52,43 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Base64;
-import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.StatsLog;
 
-import com.android.bluetooth.a2dp.A2dpService;
-import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.hid.HidService;
-import com.android.bluetooth.hfp.HeadsetService;
-import com.android.bluetooth.hfpclient.HeadsetClientService;
-import com.android.bluetooth.mapclient.MapClientService;
-import com.android.bluetooth.pan.PanService;
-import com.android.bluetooth.pbapclient.PbapClientService;
-import com.android.bluetooth.sdp.SdpManager;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.R;
-import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
+import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
+import com.android.bluetooth.gatt.GattService;
+import com.android.bluetooth.sdp.SdpManager;
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+
+import com.google.protobuf.InvalidProtocolBufferException;
 
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.Map;
-import java.util.Iterator;
-import java.util.List;
 
 public class AdapterService extends Service {
     private static final String TAG = "BluetoothAdapterService";
     private static final boolean DBG = true;
     private static final boolean VERBOSE = false;
-    private static final boolean TRACE_REF = false;
     private static final int MIN_ADVT_INSTANCES_FOR_MA = 5;
     private static final int MIN_OFFLOADED_FILTERS = 10;
     private static final int MIN_OFFLOADED_SCAN_STORAGE_BYTES = 1024;
-    //For Debugging only
-    private static int sRefCount = 0;
-    private long mBluetoothStartTime = 0;
 
     private final Object mEnergyInfoLock = new Object();
     private int mStackReportedState;
@@ -104,24 +96,27 @@
     private long mRxTimeTotalMs;
     private long mIdleTimeTotalMs;
     private long mEnergyUsedTotalVoltAmpSecMicro;
-    private SparseArray<UidTraffic> mUidTraffic = new SparseArray<>();
+    private final SparseArray<UidTraffic> mUidTraffic = new SparseArray<>();
 
-    private final ArrayList<ProfileService> mProfiles = new ArrayList<ProfileService>();
+    private final ArrayList<ProfileService> mRegisteredProfiles = new ArrayList<>();
+    private final ArrayList<ProfileService> mRunningProfiles = new ArrayList<>();
 
     public static final String ACTION_LOAD_ADAPTER_PROPERTIES =
-        "com.android.bluetooth.btservice.action.LOAD_ADAPTER_PROPERTIES";
+            "com.android.bluetooth.btservice.action.LOAD_ADAPTER_PROPERTIES";
     public static final String ACTION_SERVICE_STATE_CHANGED =
-        "com.android.bluetooth.btservice.action.STATE_CHANGED";
-    public static final String EXTRA_ACTION="action";
-    public static final int PROFILE_CONN_REJECTED  = 2;
+            "com.android.bluetooth.btservice.action.STATE_CHANGED";
+    public static final String EXTRA_ACTION = "action";
+    public static final int PROFILE_CONN_REJECTED = 2;
 
     private static final String ACTION_ALARM_WAKEUP =
-        "com.android.bluetooth.btservice.action.ALARM_WAKEUP";
+            "com.android.bluetooth.btservice.action.ALARM_WAKEUP";
 
-    public static final String BLUETOOTH_ADMIN_PERM =
-        android.Manifest.permission.BLUETOOTH_ADMIN;
+    static final String BLUETOOTH_BTSNOOP_ENABLE_PROPERTY = "persist.bluetooth.btsnoopenable";
+    private boolean mSnoopLogSettingAtEnable = false;
+
+    public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     public static final String BLUETOOTH_PRIVILEGED =
-                android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED;
     static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
     static final String LOCAL_MAC_ADDRESS_PERM = android.Manifest.permission.LOCAL_MAC_ADDRESS;
     static final String RECEIVE_MAP_PERM = android.Manifest.permission.RECEIVE_BLUETOOTH_MAP;
@@ -130,15 +125,7 @@
             "phonebook_access_permission";
     private static final String MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE =
             "message_access_permission";
-    private static final String SIM_ACCESS_PERMISSION_PREFERENCE_FILE =
-            "sim_access_permission";
-
-    private static final String[] DEVICE_TYPE_NAMES = new String[] {
-      "???",
-      "BR/EDR",
-      "LE",
-      "DUAL"
-    };
+    private static final String SIM_ACCESS_PERMISSION_PREFERENCE_FILE = "sim_access_permission";
 
     private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
 
@@ -147,38 +134,24 @@
     }
 
     private static AdapterService sAdapterService;
-    public static synchronized AdapterService getAdapterService(){
-        if (sAdapterService != null && !sAdapterService.mCleaningUp) {
-            Log.d(TAG, "getAdapterService() - returning " + sAdapterService);
-            return sAdapterService;
-        }
-        if (DBG)  {
-            if (sAdapterService == null) {
-                Log.d(TAG, "getAdapterService() - Service not available");
-            } else if (sAdapterService.mCleaningUp) {
-                Log.d(TAG,"getAdapterService() - Service is cleaning up");
-            }
-        }
-        return null;
+
+    public static synchronized AdapterService getAdapterService() {
+        Log.d(TAG, "getAdapterService() - returning " + sAdapterService);
+        return sAdapterService;
     }
 
     private static synchronized void setAdapterService(AdapterService instance) {
-        if (instance != null && !instance.mCleaningUp) {
-            if (DBG) Log.d(TAG, "setAdapterService() - set to: " + sAdapterService);
-            sAdapterService = instance;
-        } else {
-            if (DBG)  {
-                if (sAdapterService == null) {
-                    Log.d(TAG, "setAdapterService() - Service not available");
-                } else if (sAdapterService.mCleaningUp) {
-                    Log.d(TAG,"setAdapterService() - Service is cleaning up");
-                }
-            }
+        Log.d(TAG, "setAdapterService() - trying to set service to " + instance);
+        if (instance == null) {
+            return;
         }
+        sAdapterService = instance;
     }
 
-    private static synchronized void clearAdapterService() {
-        sAdapterService = null;
+    private static synchronized void clearAdapterService(AdapterService current) {
+        if (sAdapterService == current) {
+            sAdapterService = null;
+        }
     }
 
     private AdapterProperties mAdapterProperties;
@@ -190,10 +163,9 @@
     /* TODO: Consider to remove the search API from this class, if changed to use call-back */
     private SdpManager mSdpManager = null;
 
-    private boolean mProfilesStarted;
     private boolean mNativeAvailable;
     private boolean mCleaningUp;
-    private HashMap<String,Integer> mProfileServicesState = new HashMap<String,Integer>();
+    private final HashMap<String, Integer> mProfileServicesState = new HashMap<String, Integer>();
     //Only BluetoothManagerService should be registered
     private RemoteCallbackList<IBluetoothCallback> mCallbacks;
     private int mCurrentRequestId;
@@ -209,141 +181,148 @@
 
     private ProfileObserver mProfileObserver;
     private PhonePolicy mPhonePolicy;
+    private ActiveDeviceManager mActiveDeviceManager;
 
-    public AdapterService() {
-        super();
-        if (TRACE_REF) {
-            synchronized (AdapterService.class) {
-                sRefCount++;
-                debugLog("AdapterService() - REFCOUNT: CREATED. INSTANCE_COUNT" + sRefCount);
-            }
-        }
-    }
-
+    /**
+     * Register a {@link ProfileService} with AdapterService.
+     *
+     * @param profile the service being added.
+     */
     public void addProfile(ProfileService profile) {
-        synchronized (mProfiles) {
-            if (!mProfiles.contains(profile)) {
-                mProfiles.add(profile);
-            }
-        }
+        mHandler.obtainMessage(MESSAGE_PROFILE_SERVICE_REGISTERED, profile).sendToTarget();
     }
 
+    /**
+     * Unregister a ProfileService with AdapterService.
+     *
+     * @param profile the service being removed.
+     */
     public void removeProfile(ProfileService profile) {
-        synchronized (mProfiles) {
-            mProfiles.remove(profile);
-        }
+        mHandler.obtainMessage(MESSAGE_PROFILE_SERVICE_UNREGISTERED, profile).sendToTarget();
     }
 
-    public void onProfileServiceStateChanged(String serviceName, int state) {
+    /**
+     * Notify AdapterService that a ProfileService has started or stopped.
+     *
+     * @param profile the service being removed.
+     * @param state {@link BluetoothAdapter#STATE_ON} or {@link BluetoothAdapter#STATE_OFF}
+     */
+    public void onProfileServiceStateChanged(ProfileService profile, int state) {
+        if (state != BluetoothAdapter.STATE_ON && state != BluetoothAdapter.STATE_OFF) {
+            throw new IllegalArgumentException(BluetoothAdapter.nameForState(state));
+        }
         Message m = mHandler.obtainMessage(MESSAGE_PROFILE_SERVICE_STATE_CHANGED);
-        m.obj=serviceName;
+        m.obj = profile;
         m.arg1 = state;
         mHandler.sendMessage(m);
     }
 
-    private void processProfileServiceStateChanged(String serviceName, int state) {
-        boolean doUpdate=false;
-        boolean isBleTurningOn;
-        boolean isBleTurningOff;
-        boolean isTurningOn;
-        boolean isTurningOff;
+    private static final int MESSAGE_PROFILE_SERVICE_STATE_CHANGED = 1;
+    private static final int MESSAGE_PROFILE_SERVICE_REGISTERED = 2;
+    private static final int MESSAGE_PROFILE_SERVICE_UNREGISTERED = 3;
 
-        synchronized (mProfileServicesState) {
-            Integer prevState = mProfileServicesState.get(serviceName);
-            if (prevState != null && prevState != state) {
-                mProfileServicesState.put(serviceName,state);
-                doUpdate=true;
+    class AdapterServiceHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            debugLog("handleMessage() - Message: " + msg.what);
+
+            switch (msg.what) {
+                case MESSAGE_PROFILE_SERVICE_STATE_CHANGED:
+                    debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED");
+                    processProfileServiceStateChanged((ProfileService) msg.obj, msg.arg1);
+                    break;
+                case MESSAGE_PROFILE_SERVICE_REGISTERED:
+                    debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_REGISTERED");
+                    registerProfileService((ProfileService) msg.obj);
+                    break;
+                case MESSAGE_PROFILE_SERVICE_UNREGISTERED:
+                    debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_UNREGISTERED");
+                    unregisterProfileService((ProfileService) msg.obj);
+                    break;
             }
         }
 
-        if (!doUpdate) {
-            return;
-        }
-
-        synchronized (mAdapterStateMachine) {
-            isTurningOff = mAdapterStateMachine.isTurningOff();
-            isTurningOn = mAdapterStateMachine.isTurningOn();
-            isBleTurningOn = mAdapterStateMachine.isBleTurningOn();
-            isBleTurningOff = mAdapterStateMachine.isBleTurningOff();
-        }
-
-        debugLog("processProfileServiceStateChanged() - serviceName=" + serviceName +
-                 " isTurningOn=" + isTurningOn + " isTurningOff=" + isTurningOff +
-                 " isBleTurningOn=" + isBleTurningOn + " isBleTurningOff=" + isBleTurningOff);
-
-        if (isBleTurningOn) {
-            if (serviceName.equals("com.android.bluetooth.gatt.GattService")) {
-                debugLog("GattService is started");
-                mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BLE_STARTED));
+        private void registerProfileService(ProfileService profile) {
+            if (mRegisteredProfiles.contains(profile)) {
+                Log.e(TAG, profile.getName() + " already registered.");
                 return;
             }
+            mRegisteredProfiles.add(profile);
+        }
 
-        } else if(isBleTurningOff) {
-            if (serviceName.equals("com.android.bluetooth.gatt.GattService")) {
-                debugLog("GattService stopped");
-                mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BLE_STOPPED));
+        private void unregisterProfileService(ProfileService profile) {
+            if (!mRegisteredProfiles.contains(profile)) {
+                Log.e(TAG, profile.getName() + " not registered (UNREGISTERED).");
                 return;
             }
+            mRegisteredProfiles.remove(profile);
+        }
 
-        } else if (isTurningOff) {
-            //On to BLE_ON
-            //Process stop or disable pending
-            //Check if all services are stopped if so, do cleanup
-            synchronized (mProfileServicesState) {
-                Iterator<Map.Entry<String,Integer>> i = mProfileServicesState.entrySet().iterator();
-                while (i.hasNext()) {
-                    Map.Entry<String,Integer> entry = i.next();
-                    if (entry.getKey().equals("com.android.bluetooth.gatt.GattService")) continue;
-
-                    if (BluetoothAdapter.STATE_OFF != entry.getValue()) {
-                        debugLog("onProfileServiceStateChange() - Profile still running: "
-                            + entry.getKey());
+        private void processProfileServiceStateChanged(ProfileService profile, int state) {
+            switch (state) {
+                case BluetoothAdapter.STATE_ON:
+                    if (!mRegisteredProfiles.contains(profile)) {
+                        Log.e(TAG, profile.getName() + " not registered (STATE_ON).");
                         return;
                     }
-                }
-            }
-            debugLog("onProfileServiceStateChange() - All profile services stopped...");
-            //Send message to state machine
-            mProfilesStarted=false;
-            mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BREDR_STOPPED));
-
-        } else if (isTurningOn) {
-            updateInteropDatabase();
-
-            //Process start pending
-            //Check if all services are started if so, update state
-            synchronized (mProfileServicesState) {
-                Iterator<Map.Entry<String,Integer>> i = mProfileServicesState.entrySet().iterator();
-                while (i.hasNext()) {
-                    Map.Entry<String,Integer> entry = i.next();
-                    if (entry.getKey().equals("com.android.bluetooth.gatt.GattService")) continue;
-
-                    if (BluetoothAdapter.STATE_ON != entry.getValue()) {
-                        debugLog("onProfileServiceStateChange() - Profile still not running:"
-                            + entry.getKey());
+                    if (mRunningProfiles.contains(profile)) {
+                        Log.e(TAG, profile.getName() + " already running.");
                         return;
                     }
-                }
+                    mRunningProfiles.add(profile);
+                    if (GattService.class.getSimpleName().equals(profile.getName())) {
+                        enableNativeWithGuestFlag();
+                    } else if (mRegisteredProfiles.size() == Config.getSupportedProfiles().length
+                            && mRegisteredProfiles.size() == mRunningProfiles.size()) {
+                        mAdapterProperties.onBluetoothReady();
+                        updateUuids();
+                        setBluetoothClassFromConfig();
+                        mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
+                    }
+                    break;
+                case BluetoothAdapter.STATE_OFF:
+                    if (!mRegisteredProfiles.contains(profile)) {
+                        Log.e(TAG, profile.getName() + " not registered (STATE_OFF).");
+                        return;
+                    }
+                    if (!mRunningProfiles.contains(profile)) {
+                        Log.e(TAG, profile.getName() + " not running.");
+                        return;
+                    }
+                    mRunningProfiles.remove(profile);
+                    // If only GATT is left, send BREDR_STOPPED.
+                    if ((mRunningProfiles.size() == 1 && (GattService.class.getSimpleName()
+                            .equals(mRunningProfiles.get(0).getName())))) {
+                        mAdapterStateMachine.sendMessage(AdapterState.BREDR_STOPPED);
+                    } else if (mRunningProfiles.size() == 0) {
+                        disableNative();
+                        mAdapterStateMachine.sendMessage(AdapterState.BLE_STOPPED);
+                    }
+                    break;
+                default:
+                    Log.e(TAG, "Unhandled profile state: " + state);
             }
-            debugLog("onProfileServiceStateChange() - All profile services started.");
-            mProfilesStarted=true;
-            //Send message to state machine
-            mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BREDR_STARTED));
         }
     }
 
+    private final AdapterServiceHandler mHandler = new AdapterServiceHandler();
+
     private void updateInteropDatabase() {
         interopDatabaseClearNative();
 
-        String interop_string = Settings.Global.getString(getContentResolver(),
-                                            Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST);
-        if (interop_string == null) return;
-        Log.d(TAG, "updateInteropDatabase: [" + interop_string + "]");
+        String interopString = Settings.Global.getString(getContentResolver(),
+                Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST);
+        if (interopString == null) {
+            return;
+        }
+        Log.d(TAG, "updateInteropDatabase: [" + interopString + "]");
 
-        String[] entries = interop_string.split(";");
+        String[] entries = interopString.split(";");
         for (String entry : entries) {
             String[] tokens = entry.split(",");
-            if (tokens.length != 2) continue;
+            if (tokens.length != 2) {
+                continue;
+            }
 
             // Get feature
             int feature = 0;
@@ -378,7 +357,9 @@
             }
 
             // Check if address was parsed ok, otherwise, move on...
-            if (offset == 0) continue;
+            if (offset == 0) {
+                continue;
+            }
 
             // Add entry
             interopDatabaseAddNative(feature, addr, length);
@@ -389,27 +370,28 @@
     public void onCreate() {
         super.onCreate();
         debugLog("onCreate()");
-        mRemoteDevices = new RemoteDevices(this);
+        mRemoteDevices = new RemoteDevices(this, Looper.getMainLooper());
         mRemoteDevices.init();
         mBinder = new AdapterServiceBinder(this);
         mAdapterProperties = new AdapterProperties(this);
-        mAdapterStateMachine =  AdapterState.make(this, mAdapterProperties);
-        mJniCallbacks =  new JniCallbacks(mAdapterStateMachine, mAdapterProperties);
+        mAdapterStateMachine = AdapterState.make(this);
+        mJniCallbacks = new JniCallbacks(this, mAdapterProperties);
 
         // Android TV doesn't show consent dialogs for just works and encryption only le pairing
         boolean isAtvDevice = getApplicationContext().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK_ONLY);
         initNative(isAtvDevice);
-        mNativeAvailable=true;
+        mNativeAvailable = true;
         mCallbacks = new RemoteCallbackList<IBluetoothCallback>();
         //Load the name and address
         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDADDR);
         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME);
+        getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE);
         mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
         mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
         mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
-        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
-                BatteryStats.SERVICE_NAME));
+        mBatteryStats = IBatteryStats.Stub.asInterface(
+                ServiceManager.getService(BatteryStats.SERVICE_NAME));
 
         mSdpManager = SdpManager.init(this);
         registerReceiver(mAlarmBroadcastReceiver, new IntentFilter(ACTION_ALARM_WAKEUP));
@@ -426,6 +408,9 @@
             Log.i(TAG, "Phone policy disabled");
         }
 
+        mActiveDeviceManager = new ActiveDeviceManager(this, new ServiceFactory());
+        mActiveDeviceManager.start();
+
         setAdapterService(this);
 
         // First call to getSharedPreferences will result in a file read into
@@ -434,14 +419,32 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                getSharedPreferences(
-                        PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE, Context.MODE_PRIVATE);
-                getSharedPreferences(
-                        MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE, Context.MODE_PRIVATE);
+                getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
+                        Context.MODE_PRIVATE);
+                getSharedPreferences(MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE,
+                        Context.MODE_PRIVATE);
                 getSharedPreferences(SIM_ACCESS_PERMISSION_PREFERENCE_FILE, Context.MODE_PRIVATE);
                 return null;
             }
         }.execute();
+
+        try {
+            int systemUiUid = getApplicationContext().getPackageManager().getPackageUidAsUser(
+                    "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
+                    UserHandle.USER_SYSTEM);
+            Utils.setSystemUiUid(systemUiUid);
+            setSystemUiUidNative(systemUiUid);
+        } catch (PackageManager.NameNotFoundException e) {
+            // Some platforms, such as wearables do not have a system ui.
+            Log.w(TAG, "Unable to resolve SystemUI's UID.", e);
+        }
+
+        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+        getApplicationContext().registerReceiverAsUser(sUserSwitchedReceiver, UserHandle.ALL,
+                filter, null, null);
+        int fuid = ActivityManager.getCurrentUser();
+        Utils.setForegroundUserId(fuid);
+        setForegroundUserIdNative(fuid);
     }
 
     @Override
@@ -449,12 +452,15 @@
         debugLog("onBind()");
         return mBinder;
     }
+
+    @Override
     public boolean onUnbind(Intent intent) {
         debugLog("onUnbind() - calling cleanup");
         cleanup();
         return super.onUnbind(intent);
     }
 
+    @Override
     public void onDestroy() {
         debugLog("onDestroy()");
         mProfileObserver.stop();
@@ -465,20 +471,25 @@
         }
     }
 
-    void BleOnProcessStart() {
-        debugLog("BleOnProcessStart()");
+    public static final BroadcastReceiver sUserSwitchedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
+                int fuid = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                Utils.setForegroundUserId(fuid);
+                setForegroundUserIdNative(fuid);
+            }
+        }
+    };
+
+    void bringUpBle() {
+        debugLog("bleOnProcessStart()");
 
         if (getResources().getBoolean(
                 R.bool.config_bluetooth_reload_supported_profiles_when_enabled)) {
             Config.init(getApplicationContext());
         }
 
-        Class[] supportedProfileServices = Config.getSupportedProfiles();
-        //Initialize data objects
-        for (int i=0; i < supportedProfileServices.length;i++) {
-            mProfileServicesState.put(supportedProfileServices[i].getName(),BluetoothAdapter.STATE_OFF);
-        }
-
         // Reset |mRemoteDevices| whenever BLE is turned off then on
         // This is to replace the fact that |mRemoteDevices| was
         // reinitialized in previous code.
@@ -491,82 +502,136 @@
         mRemoteDevices.reset();
         mAdapterProperties.init(mRemoteDevices);
 
-        debugLog("BleOnProcessStart() - Make Bond State Machine");
+        debugLog("bleOnProcessStart() - Make Bond State Machine");
         mBondStateMachine = BondStateMachine.make(this, mAdapterProperties, mRemoteDevices);
 
-        mJniCallbacks.init(mBondStateMachine,mRemoteDevices);
+        mJniCallbacks.init(mBondStateMachine, mRemoteDevices);
 
         try {
             mBatteryStats.noteResetBleScan();
         } catch (RemoteException e) {
-            // Ignore.
+            Log.w(TAG, "RemoteException trying to send a reset to BatteryStats");
         }
+        StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, -1, null,
+                StatsLog.BLE_SCAN_STATE_CHANGED__STATE__RESET, false, false, false);
 
         //Start Gatt service
-        setGattProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_ON);
+        setProfileServiceState(GattService.class, BluetoothAdapter.STATE_ON);
     }
 
-    void startCoreServices()
-    {
+    void bringDownBle() {
+        stopGattProfileService();
+    }
+
+    void stateChangeCallback(int status) {
+        if (status == AbstractionLayer.BT_STATE_OFF) {
+            debugLog("stateChangeCallback: disableNative() completed");
+        } else if (status == AbstractionLayer.BT_STATE_ON) {
+            mAdapterStateMachine.sendMessage(AdapterState.BLE_STARTED);
+        } else {
+            Log.e(TAG, "Incorrect status " + status + " in stateChangeCallback");
+        }
+    }
+
+    /**
+     * Sets the Bluetooth CoD value of the local adapter if there exists a config value for it.
+     */
+    void setBluetoothClassFromConfig() {
+        int bluetoothClassConfig = retrieveBluetoothClassConfig();
+        if (bluetoothClassConfig != 0) {
+            mAdapterProperties.setBluetoothClass(new BluetoothClass(bluetoothClassConfig));
+        }
+    }
+
+    private int retrieveBluetoothClassConfig() {
+        return Settings.Global.getInt(
+                getContentResolver(), Settings.Global.BLUETOOTH_CLASS_OF_DEVICE, 0);
+    }
+
+    private boolean storeBluetoothClassConfig(int bluetoothClass) {
+        boolean result = Settings.Global.putInt(
+                getContentResolver(), Settings.Global.BLUETOOTH_CLASS_OF_DEVICE, bluetoothClass);
+
+        if (!result) {
+            Log.e(TAG, "Error storing BluetoothClass config - " + bluetoothClass);
+        }
+
+        return result;
+    }
+
+    void startProfileServices() {
         debugLog("startCoreServices()");
         Class[] supportedProfileServices = Config.getSupportedProfiles();
-
-        //Start profile services
-        if (!mProfilesStarted && supportedProfileServices.length >0) {
-            //Startup all profile services
-            setProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_ON);
-        }else {
-            debugLog("startCoreProfiles(): Profile Services alreay started");
-            mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BREDR_STARTED));
+        if (supportedProfileServices.length == 1 && GattService.class.getSimpleName()
+                .equals(supportedProfileServices[0].getSimpleName())) {
+            mAdapterProperties.onBluetoothReady();
+            updateUuids();
+            setBluetoothClassFromConfig();
+            mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
+        } else {
+            setAllProfileServiceStates(supportedProfileServices, BluetoothAdapter.STATE_ON);
         }
     }
 
-    void startBluetoothDisable() {
-        mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BEGIN_DISABLE));
-    }
-
-    boolean stopProfileServices() {
+    void stopProfileServices() {
+        mAdapterProperties.onBluetoothDisable();
         Class[] supportedProfileServices = Config.getSupportedProfiles();
-        if (mProfilesStarted && supportedProfileServices.length>0) {
-            setProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_OFF);
-            return true;
+        if (supportedProfileServices.length == 1 && (mRunningProfiles.size() == 1
+                && GattService.class.getSimpleName().equals(mRunningProfiles.get(0).getName()))) {
+            debugLog("stopProfileServices() - No profiles services to stop or already stopped.");
+            mAdapterStateMachine.sendMessage(AdapterState.BREDR_STOPPED);
+        } else {
+            setAllProfileServiceStates(supportedProfileServices, BluetoothAdapter.STATE_OFF);
         }
-        debugLog("stopProfileServices() - No profiles services to stop or already stopped.");
-        return false;
     }
 
-    boolean stopGattProfileService() {
-        //TODO: can optimize this instead of looping around all supported profiles
-        debugLog("stopGattProfileService()");
-        Class[] supportedProfileServices = Config.getSupportedProfiles();
-
-        setGattProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_OFF);
-        return true;
+    private void stopGattProfileService() {
+        mAdapterProperties.onBleDisable();
+        if (mRunningProfiles.size() == 0) {
+            debugLog("stopGattProfileService() - No profiles services to stop.");
+            mAdapterStateMachine.sendMessage(AdapterState.BLE_STOPPED);
+        }
+        setProfileServiceState(GattService.class, BluetoothAdapter.STATE_OFF);
     }
 
-
-     void updateAdapterState(int prevState, int newState){
-        if (mCallbacks !=null) {
-            int n=mCallbacks.beginBroadcast();
-            debugLog("updateAdapterState() - Broadcasting state to " + n + " receivers.");
-            for (int i=0; i <n;i++) {
+    void updateAdapterState(int prevState, int newState) {
+        mAdapterProperties.setState(newState);
+        if (mCallbacks != null) {
+            int n = mCallbacks.beginBroadcast();
+            debugLog("updateAdapterState() - Broadcasting state " + BluetoothAdapter.nameForState(
+                    newState) + " to " + n + " receivers.");
+            for (int i = 0; i < n; i++) {
                 try {
-                    mCallbacks.getBroadcastItem(i).onBluetoothStateChange(prevState,newState);
-                }  catch (RemoteException e) {
-                    debugLog("updateAdapterState() - Callback #" + i + " failed ("  + e + ")");
+                    mCallbacks.getBroadcastItem(i).onBluetoothStateChange(prevState, newState);
+                } catch (RemoteException e) {
+                    debugLog("updateAdapterState() - Callback #" + i + " failed (" + e + ")");
                 }
             }
             mCallbacks.finishBroadcast();
         }
+        // Turn the Adapter all the way off if we are disabling and the snoop log setting changed.
+        if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON) {
+            mSnoopLogSettingAtEnable =
+                    SystemProperties.getBoolean(BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, false);
+        } else if (newState == BluetoothAdapter.STATE_BLE_ON
+                   && prevState != BluetoothAdapter.STATE_OFF) {
+            boolean snoopLogSetting =
+                    SystemProperties.getBoolean(BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, false);
+            if (mSnoopLogSettingAtEnable != snoopLogSetting) {
+                mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_OFF);
+            }
+        }
     }
 
-    void cleanup () {
+    void cleanup() {
         debugLog("cleanup()");
         if (mCleaningUp) {
             errorLog("cleanup() - Service already starting to cleanup, ignoring request...");
             return;
         }
 
+        clearAdapterService(this);
+
         mCleaningUp = true;
 
         unregisterReceiver(mAlarmBroadcastReceiver);
@@ -580,27 +645,26 @@
         // {@link #releaseWakeLock(String lockName)}, so a synchronization is needed here.
         synchronized (this) {
             if (mWakeLock != null) {
-                if (mWakeLock.isHeld())
+                if (mWakeLock.isHeld()) {
                     mWakeLock.release();
+                }
                 mWakeLock = null;
             }
         }
 
         if (mAdapterStateMachine != null) {
             mAdapterStateMachine.doQuit();
-            mAdapterStateMachine.cleanup();
         }
 
         if (mBondStateMachine != null) {
             mBondStateMachine.doQuit();
-            mBondStateMachine.cleanup();
         }
 
         if (mRemoteDevices != null) {
             mRemoteDevices.cleanup();
         }
 
-        if(mSdpManager != null) {
+        if (mSdpManager != null) {
             mSdpManager.cleanup();
             mSdpManager = null;
         }
@@ -608,7 +672,7 @@
         if (mNativeAvailable) {
             debugLog("cleanup() - Cleaning up adapter native");
             cleanupNative();
-            mNativeAvailable=false;
+            mNativeAvailable = false;
         }
 
         if (mAdapterProperties != null) {
@@ -623,121 +687,37 @@
             mPhonePolicy.cleanup();
         }
 
+        if (mActiveDeviceManager != null) {
+            mActiveDeviceManager.cleanup();
+        }
+
         if (mProfileServicesState != null) {
             mProfileServicesState.clear();
         }
 
-        clearAdapterService();
-
         if (mBinder != null) {
             mBinder.cleanup();
             mBinder = null;  //Do not remove. Otherwise Binder leak!
         }
 
-        if (mCallbacks !=null) {
+        if (mCallbacks != null) {
             mCallbacks.kill();
         }
     }
 
-    private static final int MESSAGE_PROFILE_SERVICE_STATE_CHANGED =1;
-
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            debugLog("handleMessage() - Message: " + msg.what);
-
-            switch (msg.what) {
-                case MESSAGE_PROFILE_SERVICE_STATE_CHANGED: {
-                    debugLog("handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED");
-                    processProfileServiceStateChanged((String) msg.obj, msg.arg1);
-                }
-                    break;
-            }
-        }
-    };
-
-    @SuppressWarnings("rawtypes")
-    private void setGattProfileServiceState(Class[] services, int state) {
-        if (state != BluetoothAdapter.STATE_ON && state != BluetoothAdapter.STATE_OFF) {
-            Log.w(TAG,"setGattProfileServiceState(): invalid state...Leaving...");
-            return;
-        }
-
-        int expectedCurrentState= BluetoothAdapter.STATE_OFF;
-        int pendingState = BluetoothAdapter.STATE_TURNING_ON;
-
-        if (state == BluetoothAdapter.STATE_OFF) {
-            expectedCurrentState= BluetoothAdapter.STATE_ON;
-            pendingState = BluetoothAdapter.STATE_TURNING_OFF;
-        }
-
-        for (int i=0; i <services.length;i++) {
-            String serviceName = services[i].getName();
-            String simpleName = services[i].getSimpleName();
-
-            if (simpleName.equals("GattService")) {
-                Integer serviceState = mProfileServicesState.get(serviceName);
-
-                if(serviceState != null && serviceState != expectedCurrentState) {
-                    debugLog("setProfileServiceState() - Unable to "
-                        + (state == BluetoothAdapter.STATE_OFF ? "start" : "stop" )
-                        + " service " + serviceName
-                        + ". Invalid state: " + serviceState);
-                        continue;
-                }
-                debugLog("setProfileServiceState() - "
-                    + (state == BluetoothAdapter.STATE_OFF ? "Stopping" : "Starting")
-                    + " service " + serviceName);
-
-                mProfileServicesState.put(serviceName,pendingState);
-                Intent intent = new Intent(this,services[i]);
-                intent.putExtra(EXTRA_ACTION,ACTION_SERVICE_STATE_CHANGED);
-                intent.putExtra(BluetoothAdapter.EXTRA_STATE,state);
-                startService(intent);
-                return;
-            }
-        }
+    private void setProfileServiceState(Class service, int state) {
+        Intent intent = new Intent(this, service);
+        intent.putExtra(EXTRA_ACTION, ACTION_SERVICE_STATE_CHANGED);
+        intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
+        startService(intent);
     }
 
-
-    @SuppressWarnings("rawtypes")
-    private void setProfileServiceState(Class[] services, int state) {
-        if (state != BluetoothAdapter.STATE_ON && state != BluetoothAdapter.STATE_OFF) {
-            debugLog("setProfileServiceState() - Invalid state, leaving...");
-            return;
-        }
-
-        int expectedCurrentState= BluetoothAdapter.STATE_OFF;
-        int pendingState = BluetoothAdapter.STATE_TURNING_ON;
-        if (state == BluetoothAdapter.STATE_OFF) {
-            expectedCurrentState= BluetoothAdapter.STATE_ON;
-            pendingState = BluetoothAdapter.STATE_TURNING_OFF;
-        }
-
-        for (int i=0; i <services.length;i++) {
-            String serviceName = services[i].getName();
-            String simpleName = services[i].getSimpleName();
-
-            if (simpleName.equals("GattService")) continue;
-
-            Integer serviceState = mProfileServicesState.get(serviceName);
-            if(serviceState != null && serviceState != expectedCurrentState) {
-                debugLog("setProfileServiceState() - Unable to "
-                    + (state == BluetoothAdapter.STATE_OFF ? "start" : "stop" )
-                    + " service " + serviceName
-                    + ". Invalid state: " + serviceState);
+    private void setAllProfileServiceStates(Class[] services, int state) {
+        for (Class service : services) {
+            if (GattService.class.getSimpleName().equals(service.getSimpleName())) {
                 continue;
             }
-
-            debugLog("setProfileServiceState() - "
-                + (state == BluetoothAdapter.STATE_OFF ? "Stopping" : "Starting")
-                + " service " + serviceName);
-
-            mProfileServicesState.put(serviceName,pendingState);
-            Intent intent = new Intent(this,services[i]);
-            intent.putExtra(EXTRA_ACTION,ACTION_SERVICE_STATE_CHANGED);
-            intent.putExtra(BluetoothAdapter.EXTRA_STATE,state);
-            startService(intent);
+            setProfileServiceState(service, state);
         }
     }
 
@@ -764,81 +744,98 @@
     private static class AdapterServiceBinder extends IBluetooth.Stub {
         private AdapterService mService;
 
-        public AdapterServiceBinder(AdapterService svc) {
+        AdapterServiceBinder(AdapterService svc) {
             mService = svc;
         }
-        public boolean cleanup() {
+
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
         public AdapterService getService() {
-            if (mService  != null && mService.isAvailable()) {
+            if (mService != null && mService.isAvailable()) {
                 return mService;
             }
             return null;
         }
+
+        @Override
         public boolean isEnabled() {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isEnabled();
         }
 
+        @Override
         public int getState() {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) return  BluetoothAdapter.STATE_OFF;
+            if (service == null) {
+                return BluetoothAdapter.STATE_OFF;
+            }
             return service.getState();
         }
 
+        @Override
         public boolean enable() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
-                (!Utils.checkCaller())) {
+            if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
                 Log.w(TAG, "enable() - Not allowed for non-active user and non system user");
                 return false;
             }
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.enable();
         }
 
+        @Override
         public boolean enableNoAutoConnect() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
-                (!Utils.checkCaller())) {
+            if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
                 Log.w(TAG, "enableNoAuto() - Not allowed for non-active user and non system user");
                 return false;
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.enableNoAutoConnect();
         }
 
+        @Override
         public boolean disable() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
-                (!Utils.checkCaller())) {
+            if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
                 Log.w(TAG, "disable() - Not allowed for non-active user and non system user");
                 return false;
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.disable();
         }
 
+        @Override
         public String getAddress() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
-                (!Utils.checkCallerAllowManagedProfiles(mService))) {
+            if ((Binder.getCallingUid() != Process.SYSTEM_UID)
+                    && (!Utils.checkCallerAllowManagedProfiles(mService))) {
                 Log.w(TAG, "getAddress() - Not allowed for non-active user and non system user");
                 return null;
             }
 
             AdapterService service = getService();
-            if (service == null) return null;
+            if (service == null) {
+                return null;
+            }
             return service.getAddress();
         }
 
+        @Override
         public ParcelUuid[] getUuids() {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "getUuids() - Not allowed for non-active user");
@@ -846,22 +843,27 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return new ParcelUuid[0];
+            if (service == null) {
+                return new ParcelUuid[0];
+            }
             return service.getUuids();
         }
 
+        @Override
         public String getName() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
-                (!Utils.checkCaller())) {
+            if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
                 Log.w(TAG, "getName() - Not allowed for non-active user and non system user");
                 return null;
             }
 
             AdapterService service = getService();
-            if (service == null) return null;
+            if (service == null) {
+                return null;
+            }
             return service.getName();
         }
 
+        @Override
         public boolean setName(String name) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setName() - Not allowed for non-active user");
@@ -869,10 +871,37 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setName(name);
         }
 
+        public BluetoothClass getBluetoothClass() {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "getBluetoothClass() - Not allowed for non-active user");
+                return null;
+            }
+
+            AdapterService service = getService();
+            if (service == null) return null;
+            return service.getBluetoothClass();
+        }
+
+        public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
+            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.setBluetoothClass(bluetoothClass);
+        }
+
+        @Override
         public int getScanMode() {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "getScanMode() - Not allowed for non-active user");
@@ -880,10 +909,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return BluetoothAdapter.SCAN_MODE_NONE;
+            if (service == null) {
+                return BluetoothAdapter.SCAN_MODE_NONE;
+            }
             return service.getScanMode();
         }
 
+        @Override
         public boolean setScanMode(int mode, int duration) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setScanMode() - Not allowed for non-active user");
@@ -891,10 +923,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
-            return service.setScanMode(mode,duration);
+            if (service == null) {
+                return false;
+            }
+            return service.setScanMode(mode, duration);
         }
 
+        @Override
         public int getDiscoverableTimeout() {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "getDiscoverableTimeout() - Not allowed for non-active user");
@@ -902,10 +937,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return 0;
+            if (service == null) {
+                return 0;
+            }
             return service.getDiscoverableTimeout();
         }
 
+        @Override
         public boolean setDiscoverableTimeout(int timeout) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setDiscoverableTimeout() - Not allowed for non-active user");
@@ -913,10 +951,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setDiscoverableTimeout(timeout);
         }
 
+        @Override
         public boolean startDiscovery() {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "startDiscovery() - Not allowed for non-active user");
@@ -924,10 +965,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.startDiscovery();
         }
 
+        @Override
         public boolean cancelDiscovery() {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "cancelDiscovery() - Not allowed for non-active user");
@@ -935,10 +979,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.cancelDiscovery();
         }
 
+        @Override
         public boolean isDiscovering() {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "isDiscovering() - Not allowed for non-active user");
@@ -946,10 +993,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isDiscovering();
         }
 
+        @Override
         public long getDiscoveryEndMillis() {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "getDiscoveryEndMillis() - Not allowed for non-active user");
@@ -957,24 +1007,33 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return -1;
+            if (service == null) {
+                return -1;
+            }
             return service.getDiscoveryEndMillis();
         }
 
+        @Override
         public BluetoothDevice[] getBondedDevices() {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) return new BluetoothDevice[0];
+            if (service == null) {
+                return new BluetoothDevice[0];
+            }
             return service.getBondedDevices();
         }
 
+        @Override
         public int getAdapterConnectionState() {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) return BluetoothAdapter.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothAdapter.STATE_DISCONNECTED;
+            }
             return service.getAdapterConnectionState();
         }
 
+        @Override
         public int getProfileConnectionState(int profile) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "getProfileConnectionState- Not allowed for non-active user");
@@ -982,10 +1041,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
             return service.getProfileConnectionState(profile);
         }
 
+        @Override
         public boolean createBond(BluetoothDevice device, int transport) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "createBond() - Not allowed for non-active user");
@@ -993,10 +1055,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.createBond(device, transport, null);
         }
 
+        @Override
         public boolean createBondOutOfBand(BluetoothDevice device, int transport, OobData oobData) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "createBondOutOfBand() - Not allowed for non-active user");
@@ -1004,10 +1069,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.createBond(device, transport, oobData);
         }
 
+        @Override
         public boolean cancelBondProcess(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "cancelBondProcess() - Not allowed for non-active user");
@@ -1015,10 +1083,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.cancelBondProcess(device);
         }
 
+        @Override
         public boolean removeBond(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "removeBond() - Not allowed for non-active user");
@@ -1026,36 +1097,51 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.removeBond(device);
         }
 
+        @Override
         public int getBondState(BluetoothDevice device) {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) return BluetoothDevice.BOND_NONE;
+            if (service == null) {
+                return BluetoothDevice.BOND_NONE;
+            }
             return service.getBondState(device);
         }
 
+        @Override
         public boolean isBondingInitiatedLocally(BluetoothDevice device) {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isBondingInitiatedLocally(device);
         }
 
+        @Override
         public long getSupportedProfiles() {
             AdapterService service = getService();
-            if (service == null) return 0;
+            if (service == null) {
+                return 0;
+            }
             return service.getSupportedProfiles();
         }
 
+        @Override
         public int getConnectionState(BluetoothDevice device) {
             AdapterService service = getService();
-            if (service == null) return 0;
+            if (service == null) {
+                return 0;
+            }
             return service.getConnectionState(device);
         }
 
+        @Override
         public String getRemoteName(BluetoothDevice device) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "getRemoteName() - Not allowed for non-active user");
@@ -1063,10 +1149,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return null;
+            if (service == null) {
+                return null;
+            }
             return service.getRemoteName(device);
         }
 
+        @Override
         public int getRemoteType(BluetoothDevice device) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "getRemoteType() - Not allowed for non-active user");
@@ -1074,10 +1163,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
+            if (service == null) {
+                return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
+            }
             return service.getRemoteType(device);
         }
 
+        @Override
         public String getRemoteAlias(BluetoothDevice device) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "getRemoteAlias() - Not allowed for non-active user");
@@ -1085,10 +1177,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return null;
+            if (service == null) {
+                return null;
+            }
             return service.getRemoteAlias(device);
         }
 
+        @Override
         public boolean setRemoteAlias(BluetoothDevice device, String name) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setRemoteAlias() - Not allowed for non-active user");
@@ -1096,10 +1191,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setRemoteAlias(device, name);
         }
 
+        @Override
         public int getRemoteClass(BluetoothDevice device) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "getRemoteClass() - Not allowed for non-active user");
@@ -1107,10 +1205,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return 0;
+            if (service == null) {
+                return 0;
+            }
             return service.getRemoteClass(device);
         }
 
+        @Override
         public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "getRemoteUuids() - Not allowed for non-active user");
@@ -1118,10 +1219,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return new ParcelUuid[0];
+            if (service == null) {
+                return new ParcelUuid[0];
+            }
             return service.getRemoteUuids(device);
         }
 
+        @Override
         public boolean fetchRemoteUuids(BluetoothDevice device) {
             if (!Utils.checkCallerAllowManagedProfiles(mService)) {
                 Log.w(TAG, "fetchRemoteUuids() - Not allowed for non-active user");
@@ -1129,12 +1233,14 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.fetchRemoteUuids(device);
         }
 
 
-
+        @Override
         public boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setPin() - Not allowed for non-active user");
@@ -1142,10 +1248,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPin(device, accept, len, pinCode);
         }
 
+        @Override
         public boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setPasskey() - Not allowed for non-active user");
@@ -1153,10 +1262,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPasskey(device, accept, len, passkey);
         }
 
+        @Override
         public boolean setPairingConfirmation(BluetoothDevice device, boolean accept) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setPairingConfirmation() - Not allowed for non-active user");
@@ -1164,10 +1276,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPairingConfirmation(device, accept);
         }
 
+        @Override
         public int getPhonebookAccessPermission(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "getPhonebookAccessPermission() - Not allowed for non-active user");
@@ -1175,10 +1290,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return BluetoothDevice.ACCESS_UNKNOWN;
+            if (service == null) {
+                return BluetoothDevice.ACCESS_UNKNOWN;
+            }
             return service.getPhonebookAccessPermission(device);
         }
 
+        @Override
         public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setPhonebookAccessPermission() - Not allowed for non-active user");
@@ -1186,10 +1304,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPhonebookAccessPermission(device, value);
         }
 
+        @Override
         public int getMessageAccessPermission(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "getMessageAccessPermission() - Not allowed for non-active user");
@@ -1197,10 +1318,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return BluetoothDevice.ACCESS_UNKNOWN;
+            if (service == null) {
+                return BluetoothDevice.ACCESS_UNKNOWN;
+            }
             return service.getMessageAccessPermission(device);
         }
 
+        @Override
         public boolean setMessageAccessPermission(BluetoothDevice device, int value) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setMessageAccessPermission() - Not allowed for non-active user");
@@ -1208,10 +1332,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setMessageAccessPermission(device, value);
         }
 
+        @Override
         public int getSimAccessPermission(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "getSimAccessPermission() - Not allowed for non-active user");
@@ -1219,10 +1346,13 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return BluetoothDevice.ACCESS_UNKNOWN;
+            if (service == null) {
+                return BluetoothDevice.ACCESS_UNKNOWN;
+            }
             return service.getSimAccessPermission(device);
         }
 
+        @Override
         public boolean setSimAccessPermission(BluetoothDevice device, int value) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "setSimAccessPermission() - Not allowed for non-active user");
@@ -1230,51 +1360,46 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setSimAccessPermission(device, value);
         }
 
-        public void sendConnectionStateChange(BluetoothDevice
-                device, int profile, int state, int prevState) {
+        @Override
+        public void sendConnectionStateChange(BluetoothDevice device, int profile, int state,
+                int prevState) {
             AdapterService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.sendConnectionStateChange(device, profile, state, prevState);
         }
 
-        public ParcelFileDescriptor connectSocket(BluetoothDevice device, int type,
-                                                  ParcelUuid uuid, int port, int flag) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "connectSocket() - Not allowed for non-active user");
+        @Override
+        public IBluetoothSocketManager getSocketManager() {
+            AdapterService service = getService();
+            if (service == null) {
                 return null;
             }
-
-            AdapterService service = getService();
-            if (service == null) return null;
-            return service.connectSocket(device, type, uuid, port, flag);
+            return service.getSocketManager();
         }
 
-        public ParcelFileDescriptor createSocketChannel(int type, String serviceName,
-                                                        ParcelUuid uuid, int port, int flag) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "createSocketChannel() - Not allowed for non-active user");
-                return null;
-            }
-
-            AdapterService service = getService();
-            if (service == null) return null;
-            return service.createSocketChannel(type, serviceName, uuid, port, flag);
-        }
+        @Override
         public boolean sdpSearch(BluetoothDevice device, ParcelUuid uuid) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"sdpSea(): not allowed for non-active user");
+                Log.w(TAG, "sdpSea(): not allowed for non-active user");
                 return false;
             }
 
             AdapterService service = getService();
-            if (service == null) return false;
-            return service.sdpSearch(device,uuid);
+            if (service == null) {
+                return false;
+            }
+            return service.sdpSearch(device, uuid);
         }
 
+        @Override
         public int getBatteryLevel(BluetoothDevice device) {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "getBatteryLevel(): not allowed for non-active user");
@@ -1282,167 +1407,239 @@
             }
 
             AdapterService service = getService();
-            if (service == null) return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+            if (service == null) {
+                return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+            }
             return service.getBatteryLevel(device);
         }
 
+        @Override
+        public int getMaxConnectedAudioDevices() {
+            // don't check caller, may be called from system UI
+            AdapterService service = getService();
+            if (service == null) {
+                return AdapterProperties.MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND;
+            }
+            return service.getMaxConnectedAudioDevices();
+        }
+
+        //@Override
+        public boolean isA2dpOffloadEnabled() {
+            // don't check caller, may be called from system UI
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.isA2dpOffloadEnabled();
+        }
+
+        @Override
         public boolean factoryReset() {
             AdapterService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             service.disable();
             return service.factoryReset();
 
         }
 
+        @Override
         public void registerCallback(IBluetoothCallback cb) {
             AdapterService service = getService();
-            if (service == null) return ;
+            if (service == null) {
+                return;
+            }
             service.registerCallback(cb);
-         }
+        }
 
-         public void unregisterCallback(IBluetoothCallback cb) {
-             AdapterService service = getService();
-             if (service == null) return ;
-             service.unregisterCallback(cb);
-         }
+        @Override
+        public void unregisterCallback(IBluetoothCallback cb) {
+            AdapterService service = getService();
+            if (service == null) {
+                return;
+            }
+            service.unregisterCallback(cb);
+        }
 
-         public boolean isMultiAdvertisementSupported() {
-             AdapterService service = getService();
-             if (service == null) return false;
-             return service.isMultiAdvertisementSupported();
-         }
+        @Override
+        public boolean isMultiAdvertisementSupported() {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.isMultiAdvertisementSupported();
+        }
 
-         public boolean isOffloadedFilteringSupported() {
-             AdapterService service = getService();
-             if (service == null) return false;
-             int val = service.getNumOfOffloadedScanFilterSupported();
-             return (val >= MIN_OFFLOADED_FILTERS);
-         }
+        @Override
+        public boolean isOffloadedFilteringSupported() {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            int val = service.getNumOfOffloadedScanFilterSupported();
+            return (val >= MIN_OFFLOADED_FILTERS);
+        }
 
-         public boolean isOffloadedScanBatchingSupported() {
-             AdapterService service = getService();
-             if (service == null) return false;
-             int val = service.getOffloadedScanResultStorage();
-             return (val >= MIN_OFFLOADED_SCAN_STORAGE_BYTES);
-         }
+        @Override
+        public boolean isOffloadedScanBatchingSupported() {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            int val = service.getOffloadedScanResultStorage();
+            return (val >= MIN_OFFLOADED_SCAN_STORAGE_BYTES);
+        }
 
-         public boolean isLe2MPhySupported() {
-             AdapterService service = getService();
-             if (service == null) return false;
-             return service.isLe2MPhySupported();
-         }
+        @Override
+        public boolean isLe2MPhySupported() {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.isLe2MPhySupported();
+        }
 
-         public boolean isLeCodedPhySupported() {
-             AdapterService service = getService();
-             if (service == null) return false;
-             return service.isLeCodedPhySupported();
-         }
+        @Override
+        public boolean isLeCodedPhySupported() {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.isLeCodedPhySupported();
+        }
 
-         public boolean isLeExtendedAdvertisingSupported() {
-             AdapterService service = getService();
-             if (service == null) return false;
-             return service.isLeExtendedAdvertisingSupported();
-         }
+        @Override
+        public boolean isLeExtendedAdvertisingSupported() {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.isLeExtendedAdvertisingSupported();
+        }
 
-         public boolean isLePeriodicAdvertisingSupported() {
-             AdapterService service = getService();
-             if (service == null) return false;
-             return service.isLePeriodicAdvertisingSupported();
-         }
+        @Override
+        public boolean isLePeriodicAdvertisingSupported() {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.isLePeriodicAdvertisingSupported();
+        }
 
-         public int getLeMaximumAdvertisingDataLength() {
-             AdapterService service = getService();
-             if (service == null) return 0;
-             return service.getLeMaximumAdvertisingDataLength();
-         }
+        @Override
+        public int getLeMaximumAdvertisingDataLength() {
+            AdapterService service = getService();
+            if (service == null) {
+                return 0;
+            }
+            return service.getLeMaximumAdvertisingDataLength();
+        }
 
-         public boolean isActivityAndEnergyReportingSupported() {
-             AdapterService service = getService();
-             if (service == null) return false;
-             return service.isActivityAndEnergyReportingSupported();
-         }
+        @Override
+        public boolean isActivityAndEnergyReportingSupported() {
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.isActivityAndEnergyReportingSupported();
+        }
 
-         public BluetoothActivityEnergyInfo reportActivityInfo() {
-             AdapterService service = getService();
-             if (service == null) return null;
-             return service.reportActivityInfo();
-         }
+        @Override
+        public BluetoothActivityEnergyInfo reportActivityInfo() {
+            AdapterService service = getService();
+            if (service == null) {
+                return null;
+            }
+            return service.reportActivityInfo();
+        }
 
-         public void requestActivityInfo(ResultReceiver result) {
-             Bundle bundle = new Bundle();
-             bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY,
-                     reportActivityInfo());
-             result.send(0, bundle);
-         }
+        @Override
+        public void requestActivityInfo(ResultReceiver result) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, reportActivityInfo());
+            result.send(0, bundle);
+        }
 
-        public void onLeServiceUp(){
-             AdapterService service = getService();
-             if (service == null) return;
-             service.onLeServiceUp();
-         }
+        @Override
+        public void onLeServiceUp() {
+            AdapterService service = getService();
+            if (service == null) {
+                return;
+            }
+            service.onLeServiceUp();
+        }
 
-         public void onBrEdrDown(){
-             AdapterService service = getService();
-             if (service == null) return;
-             service.onBrEdrDown();
-         }
+        @Override
+        public void onBrEdrDown() {
+            AdapterService service = getService();
+            if (service == null) {
+                return;
+            }
+            service.onBrEdrDown();
+        }
 
-         public void dump(FileDescriptor fd, String[] args) {
+        @Override
+        public void dump(FileDescriptor fd, String[] args) {
             PrintWriter writer = new PrintWriter(new FileOutputStream(fd));
             AdapterService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.dump(fd, writer, args);
-         }
-    };
+            writer.close();
+        }
+    }
+
+    ;
 
     // ----API Methods--------
 
     public boolean isEnabled() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.getState() == BluetoothAdapter.STATE_ON;
-     }
+    }
 
-     public int getState() {
-         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-         if (mAdapterProperties != null) return mAdapterProperties.getState();
-         return BluetoothAdapter.STATE_OFF;
-     }
+    public int getState() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (mAdapterProperties != null) {
+            return mAdapterProperties.getState();
+        }
+        return BluetoothAdapter.STATE_OFF;
+    }
 
-     public boolean enable() {
-         return enable(false);
-     }
+    public boolean enable() {
+        return enable(false);
+    }
 
-     public boolean enableNoAutoConnect() {
-         return enable (true);
-     }
+    public boolean enableNoAutoConnect() {
+        return enable(true);
+    }
 
-     public synchronized boolean enable(boolean quietMode) {
-         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
-         // Enforce the user restriction for disallowing Bluetooth if it was set.
-         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM)) {
-            debugLog("enable() called when Bluetooth was disallowed");
-            return false;
-         }
-
-         debugLog("enable() - Enable called with quiet mode status =  " + mQuietmode);
-         mQuietmode = quietMode;
-         Message m = mAdapterStateMachine.obtainMessage(AdapterState.BLE_TURN_ON);
-         mAdapterStateMachine.sendMessage(m);
-         mBluetoothStartTime = System.currentTimeMillis();
-         return true;
-     }
-
-     boolean disable() {
+    public synchronized boolean enable(boolean quietMode) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
 
-        debugLog("disable() called...");
-        Message m = mAdapterStateMachine.obtainMessage(AdapterState.BLE_TURN_OFF);
-        mAdapterStateMachine.sendMessage(m);
+        // Enforce the user restriction for disallowing Bluetooth if it was set.
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM)) {
+            debugLog("enable() called when Bluetooth was disallowed");
+            return false;
+        }
+
+        debugLog("enable() - Enable called with quiet mode status =  " + quietMode);
+        mQuietmode = quietMode;
+        mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_ON);
         return true;
     }
 
-     String getAddress() {
+    boolean disable() {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+
+        debugLog("disable() called with mRunningProfiles.size() = " + mRunningProfiles.size());
+        mAdapterStateMachine.sendMessage(AdapterState.USER_TURN_OFF);
+        return true;
+    }
+
+    String getAddress() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         enforceCallingOrSelfPermission(LOCAL_MAC_ADDRESS_PERM, "Need LOCAL_MAC_ADDRESS permission");
 
@@ -1451,15 +1648,14 @@
         return Utils.getAddressStringFromByte(address);
     }
 
-     ParcelUuid[] getUuids() {
+    ParcelUuid[] getUuids() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         return mAdapterProperties.getUuids();
     }
 
     public String getName() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM,
-                                       "Need BLUETOOTH permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         try {
             return mAdapterProperties.getName();
@@ -1469,20 +1665,42 @@
         return null;
     }
 
-     boolean setName(String name) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH ADMIN permission");
+    boolean setName(String name) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
 
         return mAdapterProperties.setName(name);
     }
 
-     int getScanMode() {
+    BluetoothClass getBluetoothClass() {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+
+        return mAdapterProperties.getBluetoothClass();
+    }
+
+    /**
+     * Sets the Bluetooth CoD on the local adapter and also modifies the storage config for it.
+     *
+     * <p>Once set, this value persists across reboots.
+     */
+    boolean setBluetoothClass(BluetoothClass bluetoothClass) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH PRIVILEGED permission");
+        debugLog("setBluetoothClass() to " + bluetoothClass);
+        boolean result = mAdapterProperties.setBluetoothClass(bluetoothClass);
+        if (!result) {
+            Log.e(TAG, "setBluetoothClass() to " + bluetoothClass + " failed");
+        }
+
+        return result && storeBluetoothClassConfig(bluetoothClass.getClassOfDevice());
+    }
+
+    int getScanMode() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         return mAdapterProperties.getScanMode();
     }
 
-     boolean setScanMode(int mode, int duration) {
+    boolean setScanMode(int mode, int duration) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         setDiscoverableTimeout(duration);
@@ -1491,35 +1709,33 @@
         return mAdapterProperties.setScanMode(newMode);
     }
 
-     int getDiscoverableTimeout() {
+    int getDiscoverableTimeout() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         return mAdapterProperties.getDiscoverableTimeout();
     }
 
-     boolean setDiscoverableTimeout(int timeout) {
+    boolean setDiscoverableTimeout(int timeout) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         return mAdapterProperties.setDiscoverableTimeout(timeout);
     }
 
-     boolean startDiscovery() {
+    boolean startDiscovery() {
         debugLog("startDiscovery");
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
 
         return startDiscoveryNative();
     }
 
-     boolean cancelDiscovery() {
+    boolean cancelDiscovery() {
         debugLog("cancelDiscovery");
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
 
         return cancelDiscoveryNative();
     }
 
-     boolean isDiscovering() {
+    boolean isDiscovering() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         return mAdapterProperties.isDiscovering();
@@ -1531,6 +1747,11 @@
         return mAdapterProperties.discoveryEndMillis();
     }
 
+    /**
+     * Same as API method {@link BluetoothAdapter#getBondedDevices()}
+     *
+     * @return array of bonded {@link BluetoothDevice} or null on error
+     */
     public BluetoothDevice[] getBondedDevices() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.getBondedDevices();
@@ -1541,24 +1762,24 @@
         return mAdapterProperties.getConnectionState();
     }
 
-     int getProfileConnectionState(int profile) {
+    int getProfileConnectionState(int profile) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         return mAdapterProperties.getProfileConnectionState(profile);
     }
-     boolean sdpSearch(BluetoothDevice device,ParcelUuid uuid) {
-         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-         if(mSdpManager != null) {
-             mSdpManager.sdpSearch(device,uuid);
-             return true;
-         } else {
-             return false;
-         }
-     }
 
-     boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-            "Need BLUETOOTH ADMIN permission");
+    boolean sdpSearch(BluetoothDevice device, ParcelUuid uuid) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (mSdpManager != null) {
+            mSdpManager.sdpSearch(device, uuid);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
         if (deviceProp != null && deviceProp.getBondState() != BluetoothDevice.BOND_NONE) {
             return false;
@@ -1583,15 +1804,17 @@
         return true;
     }
 
-      public boolean isQuietModeEnabled() {
-          debugLog("isQuetModeEnabled() - Enabled = " + mQuietmode);
-          return mQuietmode;
-     }
+    public boolean isQuietModeEnabled() {
+        debugLog("isQuetModeEnabled() - Enabled = " + mQuietmode);
+        return mQuietmode;
+    }
 
     public void updateUuids() {
-        debugLog( "updateUuids() - Updating UUIDs for bonded devices");
+        debugLog("updateUuids() - Updating UUIDs for bonded devices");
         BluetoothDevice[] bondedDevices = getBondedDevices();
-        if (bondedDevices == null) return;
+        if (bondedDevices == null) {
+            return;
+        }
 
         for (BluetoothDevice device : bondedDevices) {
             mRemoteDevices.updateUuids(device);
@@ -1636,7 +1859,17 @@
         return true;
     }
 
-    int getBondState(BluetoothDevice device) {
+    /**
+     * Get the bond state of a particular {@link BluetoothDevice}
+     *
+     * @param device remote device of interest
+     * @return bond state <p>Possible values are
+     * {@link BluetoothDevice#BOND_NONE},
+     * {@link BluetoothDevice#BOND_BONDING},
+     * {@link BluetoothDevice#BOND_BONDED}.
+     */
+    @VisibleForTesting
+    public int getBondState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
         if (deviceProp == null) {
@@ -1664,32 +1897,48 @@
         return getConnectionStateNative(addr);
     }
 
-    String getRemoteName(BluetoothDevice device) {
+    /**
+     * Same as API method {@link BluetoothDevice#getName()}
+     *
+     * @param device remote device of interest
+     * @return remote device name
+     */
+    public String getRemoteName(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (mRemoteDevices == null) return null;
+        if (mRemoteDevices == null) {
+            return null;
+        }
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) return null;
+        if (deviceProp == null) {
+            return null;
+        }
         return deviceProp.getName();
     }
 
     int getRemoteType(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
+        if (deviceProp == null) {
+            return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
+        }
         return deviceProp.getDeviceType();
     }
 
     String getRemoteAlias(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) return null;
+        if (deviceProp == null) {
+            return null;
+        }
         return deviceProp.getAlias();
     }
 
     boolean setRemoteAlias(BluetoothDevice device, String name) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) return false;
+        if (deviceProp == null) {
+            return false;
+        }
         deviceProp.setAlias(device, name);
         return true;
     }
@@ -1697,15 +1946,26 @@
     int getRemoteClass(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) return 0;
+        if (deviceProp == null) {
+            return 0;
+        }
 
         return deviceProp.getBluetoothClass();
     }
 
-    ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
+    /**
+     * Get UUIDs for service supported by a remote device
+     *
+     * @param device the remote device that we want to get UUIDs from
+     * @return
+     */
+    @VisibleForTesting
+    public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) return null;
+        if (deviceProp == null) {
+            return null;
+        }
         return deviceProp.getUuids();
     }
 
@@ -1718,7 +1978,9 @@
     int getBatteryLevel(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+        if (deviceProp == null) {
+            return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+        }
         return deviceProp.getBatteryLevel();
     }
 
@@ -1726,14 +1988,14 @@
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
         // Only allow setting a pin in bonding state, or bonded state in case of security upgrade.
-        if (deviceProp == null
-                || (deviceProp.getBondState() != BluetoothDevice.BOND_BONDING
-                           && deviceProp.getBondState() != BluetoothDevice.BOND_BONDED)) {
+        if (deviceProp == null || (deviceProp.getBondState() != BluetoothDevice.BOND_BONDING
+                && deviceProp.getBondState() != BluetoothDevice.BOND_BONDED)) {
             return false;
         }
 
         if (pinCode.length != len) {
-            EventLog.writeEvent(0x534e4554, "139287605", -1, "PIN code length mismatch");
+            android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+                    "PIN code length mismatch");
             return false;
         }
 
@@ -1749,7 +2011,8 @@
         }
 
         if (passkey.length != len) {
-            EventLog.writeEvent(0x534e4554, "139287605", -1, "Passkey length mismatch");
+            android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+                    "Passkey length mismatch");
             return false;
         }
 
@@ -1759,16 +2022,16 @@
     }
 
     boolean setPairingConfirmation(BluetoothDevice device, boolean accept) {
-        enforceCallingOrSelfPermission(
-                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH PRIVILEGED permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
         if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDING) {
             return false;
         }
 
         byte[] addr = Utils.getBytesFromAddress(device.getAddress());
-        return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION,
-                accept, 0);
+        return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION, accept,
+                0);
     }
 
     int getPhonebookAccessPermission(BluetoothDevice device) {
@@ -1778,13 +2041,13 @@
         if (!pref.contains(device.getAddress())) {
             return BluetoothDevice.ACCESS_UNKNOWN;
         }
-        return pref.getBoolean(device.getAddress(), false)
-                ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_REJECTED;
+        return pref.getBoolean(device.getAddress(), false) ? BluetoothDevice.ACCESS_ALLOWED
+                : BluetoothDevice.ACCESS_REJECTED;
     }
 
     boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
-        enforceCallingOrSelfPermission(
-                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH PRIVILEGED permission");
         SharedPreferences pref = getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
                 Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = pref.edit();
@@ -1804,13 +2067,13 @@
         if (!pref.contains(device.getAddress())) {
             return BluetoothDevice.ACCESS_UNKNOWN;
         }
-        return pref.getBoolean(device.getAddress(), false)
-                ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_REJECTED;
+        return pref.getBoolean(device.getAddress(), false) ? BluetoothDevice.ACCESS_ALLOWED
+                : BluetoothDevice.ACCESS_REJECTED;
     }
 
     boolean setMessageAccessPermission(BluetoothDevice device, int value) {
-        enforceCallingOrSelfPermission(
-                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH PRIVILEGED permission");
         SharedPreferences pref = getSharedPreferences(MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE,
                 Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = pref.edit();
@@ -1825,20 +2088,20 @@
 
     int getSimAccessPermission(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        SharedPreferences pref = getSharedPreferences(SIM_ACCESS_PERMISSION_PREFERENCE_FILE,
-                Context.MODE_PRIVATE);
+        SharedPreferences pref =
+                getSharedPreferences(SIM_ACCESS_PERMISSION_PREFERENCE_FILE, Context.MODE_PRIVATE);
         if (!pref.contains(device.getAddress())) {
             return BluetoothDevice.ACCESS_UNKNOWN;
         }
-        return pref.getBoolean(device.getAddress(), false)
-                ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_REJECTED;
+        return pref.getBoolean(device.getAddress(), false) ? BluetoothDevice.ACCESS_ALLOWED
+                : BluetoothDevice.ACCESS_REJECTED;
     }
 
     boolean setSimAccessPermission(BluetoothDevice device, int value) {
-        enforceCallingOrSelfPermission(
-                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
-        SharedPreferences pref = getSharedPreferences(SIM_ACCESS_PERMISSION_PREFERENCE_FILE,
-                Context.MODE_PRIVATE);
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH PRIVILEGED permission");
+        SharedPreferences pref =
+                getSharedPreferences(SIM_ACCESS_PERMISSION_PREFERENCE_FILE, Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = pref.edit();
         if (value == BluetoothDevice.ACCESS_UNKNOWN) {
             editor.remove(device.getAddress());
@@ -1852,34 +2115,21 @@
     void sendConnectionStateChange(BluetoothDevice device, int profile, int state, int prevState) {
         // TODO(BT) permission check?
         // Since this is a binder call check if Bluetooth is on still
-        if (getState() == BluetoothAdapter.STATE_OFF) return;
+        if (getState() == BluetoothAdapter.STATE_OFF) {
+            return;
+        }
 
         mAdapterProperties.sendConnectionStateChange(device, profile, state, prevState);
 
     }
 
-    ParcelFileDescriptor connectSocket(
-            BluetoothDevice device, int type, ParcelUuid uuid, int port, int flag) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        int fd = connectSocketNative(Utils.getBytesFromAddress(device.getAddress()), type,
-                Utils.uuidToByteArray(uuid), port, flag, Binder.getCallingUid());
-        if (fd < 0) {
-            errorLog("Failed to connect socket");
+    IBluetoothSocketManager getSocketManager() {
+        android.os.IBinder obj = getSocketManagerNative();
+        if (obj == null) {
             return null;
         }
-        return ParcelFileDescriptor.adoptFd(fd);
-    }
 
-    ParcelFileDescriptor createSocketChannel(
-            int type, String serviceName, ParcelUuid uuid, int port, int flag) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        int fd = createSocketChannelNative(
-                type, serviceName, Utils.uuidToByteArray(uuid), port, flag, Binder.getCallingUid());
-        if (fd < 0) {
-            errorLog("Failed to create socket channel");
-            return null;
-        }
-        return ParcelFileDescriptor.adoptFd(fd);
+        return IBluetoothSocketManager.Stub.asInterface(obj);
     }
 
     boolean factoryReset() {
@@ -1955,10 +2205,30 @@
         return mAdapterProperties.getLeMaximumAdvertisingDataLength();
     }
 
+    /**
+     * Get the maximum number of connected audio devices.
+     *
+     * @return the maximum number of connected audio devices
+     */
+    public int getMaxConnectedAudioDevices() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.getMaxConnectedAudioDevices();
+    }
+
+    /**
+     * Check whether A2DP offload is enabled.
+     *
+     * @return true if A2DP offload is enabled
+     */
+    public boolean isA2dpOffloadEnabled() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAdapterProperties.isA2dpOffloadEnabled();
+    }
+
     private BluetoothActivityEnergyInfo reportActivityInfo() {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
-        if (mAdapterProperties.getState() != BluetoothAdapter.STATE_ON ||
-                !mAdapterProperties.isActivityAndEnergyReportingSupported()) {
+        if (mAdapterProperties.getState() != BluetoothAdapter.STATE_ON
+                || !mAdapterProperties.isActivityAndEnergyReportingSupported()) {
             return null;
         }
 
@@ -1973,11 +2243,10 @@
                 // we query.
             }
 
-            final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(
-                    SystemClock.elapsedRealtime(),
-                    mStackReportedState,
-                    mTxTimeTotalMs, mRxTimeTotalMs, mIdleTimeTotalMs,
-                    mEnergyUsedTotalVoltAmpSecMicro);
+            final BluetoothActivityEnergyInfo info =
+                    new BluetoothActivityEnergyInfo(SystemClock.elapsedRealtime(),
+                            mStackReportedState, mTxTimeTotalMs, mRxTimeTotalMs, mIdleTimeTotalMs,
+                            mEnergyUsedTotalVoltAmpSecMicro);
 
             // Count the number of entries that have byte counts > 0
             int arrayLen = 0;
@@ -1988,27 +2257,18 @@
                 }
             }
 
-            // Copy the traffic objects whose byte counts are > 0 and reset the originals.
+            // Copy the traffic objects whose byte counts are > 0
             final UidTraffic[] result = arrayLen > 0 ? new UidTraffic[arrayLen] : null;
             int putIdx = 0;
             for (int i = 0; i < mUidTraffic.size(); i++) {
                 final UidTraffic traffic = mUidTraffic.valueAt(i);
                 if (traffic.getTxBytes() != 0 || traffic.getRxBytes() != 0) {
                     result[putIdx++] = traffic.clone();
-                    traffic.setRxBytes(0);
-                    traffic.setTxBytes(0);
                 }
             }
 
             info.setUidTraffic(result);
 
-            // Read on clear values; a record of data is created with
-            // timstamp and new samples are collected until read again
-            mStackReportedState = 0;
-            mTxTimeTotalMs = 0;
-            mRxTimeTotalMs = 0;
-            mIdleTimeTotalMs = 0;
-            mEnergyUsedTotalVoltAmpSecMicro = 0;
             return info;
         }
     }
@@ -2018,14 +2278,12 @@
         return mAdapterProperties.getTotalNumOfTrackableAdvertisements();
     }
 
-    public void onLeServiceUp() {
-        Message m = mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_ON);
-        mAdapterStateMachine.sendMessage(m);
+    void onLeServiceUp() {
+        mAdapterStateMachine.sendMessage(AdapterState.USER_TURN_ON);
     }
 
-    public void onBrEdrDown() {
-        Message m = mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_OFF);
-        mAdapterStateMachine.sendMessage(m);
+    void onBrEdrDown() {
+        mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_OFF);
     }
 
     private static int convertScanModeToHal(int mode) {
@@ -2065,10 +2323,11 @@
 
             long wakeupTime = SystemClock.elapsedRealtime() + delayMillis;
             int type = shouldWake ? AlarmManager.ELAPSED_REALTIME_WAKEUP
-                                  : AlarmManager.ELAPSED_REALTIME;
+                    : AlarmManager.ELAPSED_REALTIME;
 
             Intent intent = new Intent(ACTION_ALARM_WAKEUP);
-            mPendingAlarm = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+            mPendingAlarm =
+                    PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
             mAlarmManager.setExact(type, wakeupTime, mPendingAlarm);
             return true;
         }
@@ -2086,8 +2345,9 @@
                 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, lockName);
             }
 
-            if (!mWakeLock.isHeld())
+            if (!mWakeLock.isHeld()) {
                 mWakeLock.acquire();
+            }
         }
         return true;
     }
@@ -2103,24 +2363,25 @@
                 return false;
             }
 
-            if (mWakeLock.isHeld())
+            if (mWakeLock.isHeld()) {
                 mWakeLock.release();
+            }
         }
         return true;
     }
 
-    private void energyInfoCallback(int status, int ctrl_state, long tx_time, long rx_time,
-            long idle_time, long energy_used, UidTraffic[] data) throws RemoteException {
-        if (ctrl_state >= BluetoothActivityEnergyInfo.BT_STACK_STATE_INVALID
-                && ctrl_state <= BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_IDLE) {
+    private void energyInfoCallback(int status, int ctrlState, long txTime, long rxTime,
+            long idleTime, long energyUsed, UidTraffic[] data) throws RemoteException {
+        if (ctrlState >= BluetoothActivityEnergyInfo.BT_STACK_STATE_INVALID
+                && ctrlState <= BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_IDLE) {
             // Energy is product of mA, V and ms. If the chipset doesn't
             // report it, we have to compute it from time
-            if (energy_used == 0) {
+            if (energyUsed == 0) {
                 try {
-                    final long txMah = Math.multiplyExact(tx_time, getTxCurrentMa());
-                    final long rxMah = Math.multiplyExact(rx_time, getRxCurrentMa());
-                    final long idleMah = Math.multiplyExact(idle_time, getIdleCurrentMa());
-                    energy_used = (long) (Math.addExact(Math.addExact(txMah, rxMah), idleMah)
+                    final long txMah = Math.multiplyExact(txTime, getTxCurrentMa());
+                    final long rxMah = Math.multiplyExact(rxTime, getRxCurrentMa());
+                    final long idleMah = Math.multiplyExact(idleTime, getIdleCurrentMa());
+                    energyUsed = (long) (Math.addExact(Math.addExact(txMah, rxMah), idleMah)
                             * getOperatingVolt());
                 } catch (ArithmeticException e) {
                     Slog.wtf(TAG, "overflow in bluetooth energy callback", e);
@@ -2129,16 +2390,16 @@
             }
 
             synchronized (mEnergyInfoLock) {
-                mStackReportedState = ctrl_state;
+                mStackReportedState = ctrlState;
                 long totalTxTimeMs;
                 long totalRxTimeMs;
                 long totalIdleTimeMs;
                 long totalEnergy;
                 try {
-                    totalTxTimeMs = Math.addExact(mTxTimeTotalMs, tx_time);
-                    totalRxTimeMs = Math.addExact(mRxTimeTotalMs, rx_time);
-                    totalIdleTimeMs = Math.addExact(mIdleTimeTotalMs, idle_time);
-                    totalEnergy = Math.addExact(mEnergyUsedTotalVoltAmpSecMicro, energy_used);
+                    totalTxTimeMs = Math.addExact(mTxTimeTotalMs, txTime);
+                    totalRxTimeMs = Math.addExact(mRxTimeTotalMs, rxTime);
+                    totalIdleTimeMs = Math.addExact(mIdleTimeTotalMs, idleTime);
+                    totalEnergy = Math.addExact(mEnergyUsedTotalVoltAmpSecMicro, energyUsed);
                 } catch (ArithmeticException e) {
                     // This could be because we accumulated a lot of time, or we got a very strange
                     // value from the controller (more likely). Discard this data.
@@ -2167,9 +2428,9 @@
             }
         }
 
-        verboseLog("energyInfoCallback() status = " + status + "tx_time = " + tx_time + "rx_time = "
-                + rx_time + "idle_time = " + idle_time + "energy_used = " + energy_used
-                + "ctrl_state = " + ctrl_state + "traffic = " + Arrays.toString(data));
+        verboseLog("energyInfoCallback() status = " + status + "txTime = " + txTime + "rxTime = "
+                + rxTime + "idleTime = " + idleTime + "energyUsed = " + energyUsed + "ctrlState = "
+                + ctrlState + "traffic = " + Arrays.toString(data));
     }
 
     private int getIdleCurrentMa() {
@@ -2198,31 +2459,22 @@
             return;
         }
 
-        if (args.length > 0) {
-            verboseLog(
-                    "dumpsys arguments, check for protobuf output: " + TextUtils.join(" ", args));
-            if (args[0].startsWith("--proto")) {
-                if (args[0].equals("--proto-java-bin")) {
-                    dumpJava(fd);
-                } else {
-                    dumpNative(fd, args);
-                }
-                return;
-            }
+        verboseLog("dumpsys arguments, check for protobuf output: " + TextUtils.join(" ", args));
+        if (args[0].equals("--proto-bin")) {
+            dumpMetrics(fd);
+            return;
         }
 
-        writer.println("Bonded devices:");
-        for (BluetoothDevice device : getBondedDevices()) {
-            writer.println("  " + device.getAddress() + " [" + DEVICE_TYPE_NAMES[device.getType()]
-                    + "] " + device.getName());
-        }
+        writer.println();
+        mAdapterProperties.dump(fd, writer, args);
+        writer.println("mSnoopLogSettingAtEnable = " + mSnoopLogSettingAtEnable);
 
-        // Dump profile information
+        writer.println();
+        mAdapterStateMachine.dump(fd, writer, args);
+
         StringBuilder sb = new StringBuilder();
-        synchronized (mProfiles) {
-            for (ProfileService profile : mProfiles) {
-                profile.dump(sb);
-            }
+        for (ProfileService profile : mRegisteredProfiles) {
+            profile.dump(sb);
         }
 
         writer.write(sb.toString());
@@ -2231,30 +2483,43 @@
         dumpNative(fd, args);
     }
 
-    private void dumpJava(FileDescriptor fd) {
-        BluetoothProto.BluetoothLog log = new BluetoothProto.BluetoothLog();
-        log.setNumBondedDevices(getBondedDevices().length);
-
-        for (ProfileService profile : mProfiles) {
-            profile.dumpProto(log);
+    private void dumpMetrics(FileDescriptor fd) {
+        BluetoothMetricsProto.BluetoothLog.Builder metricsBuilder =
+                BluetoothMetricsProto.BluetoothLog.newBuilder();
+        byte[] nativeMetricsBytes = dumpMetricsNative();
+        debugLog("dumpMetrics: native metrics size is " + nativeMetricsBytes.length);
+        if (nativeMetricsBytes.length > 0) {
+            try {
+                metricsBuilder.mergeFrom(nativeMetricsBytes);
+            } catch (InvalidProtocolBufferException ex) {
+                Log.w(TAG, "dumpMetrics: problem parsing metrics protobuf, " + ex.getMessage());
+                return;
+            }
         }
-
-        try {
-            FileOutputStream protoOut = new FileOutputStream(fd);
-            String protoOutString = Base64.encodeToString(log.toByteArray(), Base64.DEFAULT);
-            protoOut.write(protoOutString.getBytes(StandardCharsets.UTF_8));
-            protoOut.close();
+        metricsBuilder.setNumBondedDevices(getBondedDevices().length);
+        MetricsLogger.dumpProto(metricsBuilder);
+        for (ProfileService profile : mRegisteredProfiles) {
+            profile.dumpProto(metricsBuilder);
+        }
+        byte[] metricsBytes = Base64.encode(metricsBuilder.build().toByteArray(), Base64.DEFAULT);
+        debugLog("dumpMetrics: combined metrics size is " + metricsBytes.length);
+        try (FileOutputStream protoOut = new FileOutputStream(fd)) {
+            protoOut.write(metricsBytes);
         } catch (IOException e) {
-            errorLog("Unable to write Java protobuf to file descriptor.");
+            errorLog("dumpMetrics: error writing combined protobuf to fd, " + e.getMessage());
         }
     }
 
     private void debugLog(String msg) {
-        if (DBG) Log.d(TAG, msg);
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
     }
 
     private void verboseLog(String msg) {
-        if (VERBOSE) Log.v(TAG, msg);
+        if (VERBOSE) {
+            Log.v(TAG, msg);
+        }
     }
 
     private void errorLog(String msg) {
@@ -2271,61 +2536,95 @@
         }
     };
 
-    private native static void classInitNative();
-    private native boolean initNative(boolean isAtvDevice);
-    private native void cleanupNative();
-    /*package*/ native boolean enableNative(boolean startRestricted);
-    /*package*/ native boolean disableNative();
-    /*package*/ native boolean setAdapterPropertyNative(int type, byte[] val);
-    /*package*/ native boolean getAdapterPropertiesNative();
-    /*package*/ native boolean getAdapterPropertyNative(int type);
-    /*package*/ native boolean setAdapterPropertyNative(int type);
-    /*package*/ native boolean setDevicePropertyNative(byte[] address, int type, byte[] val);
-    /*package*/ native boolean getDevicePropertyNative(byte[] address, int type);
+    private void enableNativeWithGuestFlag() {
+        boolean isGuest = UserManager.get(this).isGuestUser();
+        if (!enableNative(isGuest)) {
+            Log.e(TAG, "enableNative() returned false");
+        }
+    }
 
-    /*package*/ native boolean createBondNative(byte[] address, int transport);
-    /*package*/ native boolean createBondOutOfBandNative(byte[] address, int transport, OobData oobData);
-    /*package*/ native boolean removeBondNative(byte[] address);
-    /*package*/ native boolean cancelBondNative(byte[] address);
-    /*package*/ native boolean sdpSearchNative(byte[] address, byte[] uuid);
+    static native void classInitNative();
 
-    /*package*/ native int getConnectionStateNative(byte[] address);
+    native boolean initNative(boolean isAtvDevice);
+
+    native void cleanupNative();
+
+    /*package*/
+    native boolean enableNative(boolean startRestricted);
+
+    /*package*/
+    native boolean disableNative();
+
+    /*package*/
+    native boolean setAdapterPropertyNative(int type, byte[] val);
+
+    /*package*/
+    native boolean getAdapterPropertiesNative();
+
+    /*package*/
+    native boolean getAdapterPropertyNative(int type);
+
+    /*package*/
+    native boolean setAdapterPropertyNative(int type);
+
+    /*package*/
+    native boolean setDevicePropertyNative(byte[] address, int type, byte[] val);
+
+    /*package*/
+    native boolean getDevicePropertyNative(byte[] address, int type);
+
+    /*package*/
+    native boolean createBondNative(byte[] address, int transport);
+
+    /*package*/
+    native boolean createBondOutOfBandNative(byte[] address, int transport, OobData oobData);
+
+    /*package*/
+    native boolean removeBondNative(byte[] address);
+
+    /*package*/
+    native boolean cancelBondNative(byte[] address);
+
+    /*package*/
+    native boolean sdpSearchNative(byte[] address, byte[] uuid);
+
+    /*package*/
+    native int getConnectionStateNative(byte[] address);
 
     private native boolean startDiscoveryNative();
+
     private native boolean cancelDiscoveryNative();
 
     private native boolean pinReplyNative(byte[] address, boolean accept, int len, byte[] pin);
-    private native boolean sspReplyNative(byte[] address, int type, boolean
-            accept, int passkey);
 
-    /*package*/ native boolean getRemoteServicesNative(byte[] address);
-    /*package*/ native boolean getRemoteMasInstancesNative(byte[] address);
+    private native boolean sspReplyNative(byte[] address, int type, boolean accept, int passkey);
+
+    /*package*/
+    native boolean getRemoteServicesNative(byte[] address);
+
+    /*package*/
+    native boolean getRemoteMasInstancesNative(byte[] address);
 
     private native int readEnergyInfo();
-    // TODO(BT) move this to ../btsock dir
-    private native int connectSocketNative(
-            byte[] address, int type, byte[] uuid, int port, int flag, int callingUid);
-    private native int createSocketChannelNative(
-            int type, String serviceName, byte[] uuid, int port, int flag, int callingUid);
 
-    /*package*/ native boolean factoryResetNative();
+    private native IBinder getSocketManagerNative();
+
+    private native void setSystemUiUidNative(int systemUiUid);
+
+    private static native void setForegroundUserIdNative(int foregroundUserId);
+
+    /*package*/
+    native boolean factoryResetNative();
 
     private native void alarmFiredNative();
+
     private native void dumpNative(FileDescriptor fd, String[] arguments);
 
-    private native void interopDatabaseClearNative();
-    private native void interopDatabaseAddNative(int feature, byte[] address, int length);
+    private native byte[] dumpMetricsNative();
 
-    protected void finalize() {
-        debugLog("finalize() - clean up object " + this);
-        cleanup();
-        if (TRACE_REF) {
-            synchronized (AdapterService.class) {
-                sRefCount--;
-                debugLog("finalize() - REFCOUNT: FINALIZED. INSTANCE_COUNT= " + sRefCount);
-            }
-        }
-    }
+    private native void interopDatabaseClearNative();
+
+    private native void interopDatabaseAddNative(int feature, byte[] address, int length);
 
     // 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
diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java
index 78f43d0..94feef2 100644
--- a/src/com/android/bluetooth/btservice/AdapterState.java
+++ b/src/com/android/bluetooth/btservice/AdapterState.java
@@ -18,7 +18,6 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.os.Message;
-import android.os.UserManager;
 import android.util.Log;
 
 import com.android.internal.util.State;
@@ -26,91 +25,101 @@
 
 /**
  * This state machine handles Bluetooth Adapter State.
- * States:
- *      {@link OnState} : Bluetooth is on at this state
- *      {@link OffState}: Bluetooth is off at this state. This is the initial
- *      state.
- *      {@link PendingCommandState} : An enable / disable operation is pending.
- * TODO(BT): Add per process on state.
+ * Stable States:
+ *      {@link OffState}: Initial State
+ *      {@link BleOnState} : Bluetooth Low Energy, Including GATT, is on
+ *      {@link OnState} : Bluetooth is on (All supported profiles)
+ *
+ * Transition States:
+ *      {@link TurningBleOnState} : OffState to BleOnState
+ *      {@link TurningBleOffState} : BleOnState to OffState
+ *      {@link TurningOnState} : BleOnState to OnState
+ *      {@link TurningOffState} : OnState to BleOnState
+ *
+ *        +------   Off  <-----+
+ *        |                    |
+ *        v                    |
+ * TurningBleOn   TO--->   TurningBleOff
+ *        |                  ^ ^
+ *        |                  | |
+ *        +----->        ----+ |
+ *                 BleOn       |
+ *        +------        <---+ O
+ *        v                  | T
+ *    TurningOn  TO---->  TurningOff
+ *        |                    ^
+ *        |                    |
+ *        +----->   On   ------+
+ *
  */
 
 final class AdapterState extends StateMachine {
     private static final boolean DBG = true;
-    private static final boolean VDBG = true;
-    private static final String TAG = "BluetoothAdapterState";
+    private static final String TAG = AdapterState.class.getSimpleName();
 
-    static final int BLE_TURN_ON = 0;
     static final int USER_TURN_ON = 1;
-    static final int BREDR_STARTED=2;
-    static final int ENABLED_READY = 3;
-    static final int BLE_STARTED=4;
+    static final int USER_TURN_OFF = 2;
+    static final int BLE_TURN_ON = 3;
+    static final int BLE_TURN_OFF = 4;
+    static final int BREDR_STARTED = 5;
+    static final int BREDR_STOPPED = 6;
+    static final int BLE_STARTED = 7;
+    static final int BLE_STOPPED = 8;
+    static final int BREDR_START_TIMEOUT = 9;
+    static final int BREDR_STOP_TIMEOUT = 10;
+    static final int BLE_STOP_TIMEOUT = 11;
+    static final int BLE_START_TIMEOUT = 12;
 
-    static final int USER_TURN_OFF = 20;
-    static final int BEGIN_DISABLE = 21;
-    static final int ALL_DEVICES_DISCONNECTED = 22;
-    static final int BLE_TURN_OFF = 23;
+    static final int BLE_START_TIMEOUT_DELAY = 4000;
+    static final int BLE_STOP_TIMEOUT_DELAY = 1000;
+    static final int BREDR_START_TIMEOUT_DELAY = 4000;
+    static final int BREDR_STOP_TIMEOUT_DELAY = 4000;
 
-    static final int DISABLED = 24;
-    static final int BLE_STOPPED=25;
-    static final int BREDR_STOPPED = 26;
-
-    static final int BREDR_START_TIMEOUT = 100;
-    static final int ENABLE_TIMEOUT = 101;
-    static final int DISABLE_TIMEOUT = 103;
-    static final int BLE_STOP_TIMEOUT = 104;
-    static final int SET_SCAN_MODE_TIMEOUT = 105;
-    static final int BLE_START_TIMEOUT = 106;
-    static final int BREDR_STOP_TIMEOUT = 107;
-
-    static final int USER_TURN_OFF_DELAY_MS=500;
-
-    //TODO: tune me
-    private static final int ENABLE_TIMEOUT_DELAY = 12000;
-    private static final int DISABLE_TIMEOUT_DELAY = 8000;
-    private static final int BREDR_START_TIMEOUT_DELAY = 4000;
-    //BLE_START_TIMEOUT can happen quickly as it just a start gattservice
-    private static final int BLE_START_TIMEOUT_DELAY = 2000; //To start GattService
-    private static final int BLE_STOP_TIMEOUT_DELAY = 2000;
-    //BREDR_STOP_TIMEOUT can < STOP_TIMEOUT
-    private static final int BREDR_STOP_TIMEOUT_DELAY = 4000;
-    private static final int PROPERTY_OP_DELAY =2000;
     private AdapterService mAdapterService;
-    private AdapterProperties mAdapterProperties;
-    private PendingCommandState mPendingCommandState = new PendingCommandState();
+    private TurningOnState mTurningOnState = new TurningOnState();
+    private TurningBleOnState mTurningBleOnState = new TurningBleOnState();
+    private TurningOffState mTurningOffState = new TurningOffState();
+    private TurningBleOffState mTurningBleOffState = new TurningBleOffState();
     private OnState mOnState = new OnState();
     private OffState mOffState = new OffState();
     private BleOnState mBleOnState = new BleOnState();
 
-    public boolean isTurningOn() {
-        return mPendingCommandState.isTurningOn();
-    }
+    private int mPrevState = BluetoothAdapter.STATE_OFF;
 
-    public boolean isBleTurningOn() {
-        return mPendingCommandState.isBleTurningOn();
-    }
-
-    public boolean isBleTurningOff() {
-        return mPendingCommandState.isBleTurningOff();
-    }
-
-    public boolean isTurningOff() {
-        return mPendingCommandState.isTurningOff();
-    }
-
-    private AdapterState(AdapterService service, AdapterProperties adapterProperties) {
-        super("BluetoothAdapterState:");
+    private AdapterState(AdapterService service) {
+        super(TAG);
         addState(mOnState);
         addState(mBleOnState);
         addState(mOffState);
-        addState(mPendingCommandState);
+        addState(mTurningOnState);
+        addState(mTurningOffState);
+        addState(mTurningBleOnState);
+        addState(mTurningBleOffState);
         mAdapterService = service;
-        mAdapterProperties = adapterProperties;
         setInitialState(mOffState);
     }
 
-    public static AdapterState make(AdapterService service, AdapterProperties adapterProperties) {
+    private String messageString(int message) {
+        switch (message) {
+            case BLE_TURN_ON: return "BLE_TURN_ON";
+            case USER_TURN_ON: return "USER_TURN_ON";
+            case BREDR_STARTED: return "BREDR_STARTED";
+            case BLE_STARTED: return "BLE_STARTED";
+            case USER_TURN_OFF: return "USER_TURN_OFF";
+            case BLE_TURN_OFF: return "BLE_TURN_OFF";
+            case BLE_STOPPED: return "BLE_STOPPED";
+            case BREDR_STOPPED: return "BREDR_STOPPED";
+            case BLE_START_TIMEOUT: return "BLE_START_TIMEOUT";
+            case BLE_STOP_TIMEOUT: return "BLE_STOP_TIMEOUT";
+            case BREDR_START_TIMEOUT: return "BREDR_START_TIMEOUT";
+            case BREDR_STOP_TIMEOUT: return "BREDR_STOP_TIMEOUT";
+            default: return "Unknown message (" + message + ")";
+        }
+    }
+
+    public static AdapterState make(AdapterService service) {
         Log.d(TAG, "make() - Creating AdapterState");
-        AdapterState as = new AdapterState(service, adapterProperties);
+        AdapterState as = new AdapterState(service);
         as.start();
         return as;
     }
@@ -119,425 +128,272 @@
         quitNow();
     }
 
-    public void cleanup() {
-        if(mAdapterProperties != null)
-            mAdapterProperties = null;
-        if(mAdapterService != null)
+    private void cleanup() {
+        if (mAdapterService != null) {
             mAdapterService = null;
+        }
     }
 
-    private class OffState extends State {
+    @Override
+    protected void onQuitting() {
+        cleanup();
+    }
+
+    @Override
+    protected String getLogRecString(Message msg) {
+        return messageString(msg.what);
+    }
+
+    private abstract class BaseAdapterState extends State {
+
+        abstract int getStateValue();
+
         @Override
         public void enter() {
-            infoLog("Entering OffState");
+            int currState = getStateValue();
+            infoLog("entered ");
+            mAdapterService.updateAdapterState(mPrevState, currState);
+            mPrevState = currState;
+        }
+
+        void infoLog(String msg) {
+            if (DBG) {
+                Log.i(TAG, BluetoothAdapter.nameForState(getStateValue()) + " : " + msg);
+            }
+        }
+
+        void errorLog(String msg) {
+            Log.e(TAG, BluetoothAdapter.nameForState(getStateValue()) + " : " + msg);
+        }
+    }
+
+    private class OffState extends BaseAdapterState {
+
+        @Override
+        int getStateValue() {
+            return BluetoothAdapter.STATE_OFF;
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            AdapterService adapterService = mAdapterService;
-            if (adapterService == null) {
-                errorLog("Received message in OffState after cleanup: " + msg.what);
-                return false;
-            }
-
-            debugLog("Current state: OFF, message: " + msg.what);
-
-            switch(msg.what) {
-               case BLE_TURN_ON:
-                   notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON);
-                   mPendingCommandState.setBleTurningOn(true);
-                   transitionTo(mPendingCommandState);
-                   sendMessageDelayed(BLE_START_TIMEOUT, BLE_START_TIMEOUT_DELAY);
-                   adapterService.BleOnProcessStart();
-                   break;
-
-               case USER_TURN_OFF:
-                   //TODO: Handle case of service started and stopped without enable
-                   break;
-
-               default:
-                   return false;
-            }
-            return true;
-        }
-    }
-
-    private class BleOnState extends State {
-        @Override
-        public void enter() {
-            infoLog("Entering BleOnState");
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-
-            AdapterService adapterService = mAdapterService;
-            AdapterProperties adapterProperties = mAdapterProperties;
-            if ((adapterService == null) || (adapterProperties == null)) {
-                errorLog("Received message in BleOnState after cleanup: " + msg.what);
-                return false;
-            }
-
-            debugLog("Current state: BLE ON, message: " + msg.what);
-
-            switch(msg.what) {
-               case USER_TURN_ON:
-                   notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON);
-                   mPendingCommandState.setTurningOn(true);
-                   transitionTo(mPendingCommandState);
-                   sendMessageDelayed(BREDR_START_TIMEOUT, BREDR_START_TIMEOUT_DELAY);
-                   adapterService.startCoreServices();
-                   break;
-
-               case USER_TURN_OFF:
-                   notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF);
-                   mPendingCommandState.setBleTurningOff(true);
-                   adapterProperties.onBleDisable();
-                   transitionTo(mPendingCommandState);
-                   sendMessageDelayed(DISABLE_TIMEOUT, DISABLE_TIMEOUT_DELAY);
-                   boolean ret = adapterService.disableNative();
-                   if (!ret) {
-                        removeMessages(DISABLE_TIMEOUT);
-                        errorLog("Error while calling disableNative");
-                        //FIXME: what about post enable services
-                        mPendingCommandState.setBleTurningOff(false);
-                        notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
-                   }
-                   break;
-
-               default:
-                   return false;
-            }
-            return true;
-        }
-    }
-
-    private class OnState extends State {
-        @Override
-        public void enter() {
-            infoLog("Entering OnState");
-
-            AdapterService adapterService = mAdapterService;
-            if (adapterService == null) {
-                errorLog("Entered OnState after cleanup");
-                return;
-            }
-            adapterService.updateUuids();
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            AdapterProperties adapterProperties = mAdapterProperties;
-            if (adapterProperties == null) {
-                errorLog("Received message in OnState after cleanup: " + msg.what);
-                return false;
-            }
-
-            debugLog("Current state: ON, message: " + msg.what);
-
-            switch(msg.what) {
-               case BLE_TURN_OFF:
-                   notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_OFF);
-                   mPendingCommandState.setTurningOff(true);
-                   transitionTo(mPendingCommandState);
-
-                   // Invoke onBluetoothDisable which shall trigger a
-                   // setScanMode to SCAN_MODE_NONE
-                   Message m = obtainMessage(SET_SCAN_MODE_TIMEOUT);
-                   sendMessageDelayed(m, PROPERTY_OP_DELAY);
-                   adapterProperties.onBluetoothDisable();
-                   break;
-
-               case USER_TURN_ON:
-                   break;
-
-               default:
-                   return false;
-            }
-            return true;
-        }
-    }
-
-    private class PendingCommandState extends State {
-        private boolean mIsTurningOn;
-        private boolean mIsTurningOff;
-        private boolean mIsBleTurningOn;
-        private boolean mIsBleTurningOff;
-
-        public void enter() {
-            infoLog("Entering PendingCommandState");
-        }
-
-        public void setTurningOn(boolean isTurningOn) {
-            mIsTurningOn = isTurningOn;
-        }
-
-        public boolean isTurningOn() {
-            return mIsTurningOn;
-        }
-
-        public void setTurningOff(boolean isTurningOff) {
-            mIsTurningOff = isTurningOff;
-        }
-
-        public boolean isTurningOff() {
-            return mIsTurningOff;
-        }
-
-        public void setBleTurningOn(boolean isBleTurningOn) {
-            mIsBleTurningOn = isBleTurningOn;
-        }
-
-        public boolean isBleTurningOn() {
-            return mIsBleTurningOn;
-        }
-
-        public void setBleTurningOff(boolean isBleTurningOff) {
-            mIsBleTurningOff = isBleTurningOff;
-        }
-
-        public boolean isBleTurningOff() {
-            return mIsBleTurningOff;
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-
-            /* Cache current states */
-            /* TODO(eisenbach): Not sure why this is done at all.
-             * Seems like the mIs* variables should be protected,
-             * or really, removed. Which reminds me: This file needs
-             * a serious refactor...*/
-            boolean isTurningOn = isTurningOn();
-            boolean isTurningOff = isTurningOff();
-            boolean isBleTurningOn = isBleTurningOn();
-            boolean isBleTurningOff = isBleTurningOff();
-
-            logTransientStates();
-
-            AdapterService adapterService = mAdapterService;
-            AdapterProperties adapterProperties = mAdapterProperties;
-            if ((adapterService == null) || (adapterProperties == null)) {
-                errorLog("Received message in PendingCommandState after cleanup: " + msg.what);
-                return false;
-            }
-
-            debugLog("Current state: PENDING_COMMAND, message: " + msg.what);
-
             switch (msg.what) {
-                case USER_TURN_ON:
-                    if (isBleTurningOff || isTurningOff) { //TODO:do we need to send it after ble turn off also??
-                        infoLog("Deferring USER_TURN_ON request...");
-                        deferMessage(msg);
-                    }
-                    break;
-
-                case USER_TURN_OFF:
-                    if (isTurningOn || isBleTurningOn) {
-                        infoLog("Deferring USER_TURN_OFF request...");
-                        deferMessage(msg);
-                    }
-                    break;
-
                 case BLE_TURN_ON:
-                    if (isTurningOff || isBleTurningOff) {
-                        infoLog("Deferring BLE_TURN_ON request...");
-                        deferMessage(msg);
-                    }
-                    break;
-
-                case BLE_TURN_OFF:
-                    if (isTurningOn || isBleTurningOn) {
-                        infoLog("Deferring BLE_TURN_OFF request...");
-                        deferMessage(msg);
-                    }
-                    break;
-
-                case BLE_STARTED:
-                    //Remove start timeout
-                    removeMessages(BLE_START_TIMEOUT);
-
-                    //Enable
-                    boolean isGuest = UserManager.get(mAdapterService).isGuestUser();
-                    if (!adapterService.enableNative(isGuest)) {
-                        errorLog("Error while turning Bluetooth on");
-                        notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
-                        transitionTo(mOffState);
-                    } else {
-                        sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY);
-                    }
-                    break;
-
-                case BREDR_STARTED:
-                    //Remove start timeout
-                    removeMessages(BREDR_START_TIMEOUT);
-                    adapterProperties.onBluetoothReady();
-                    mPendingCommandState.setTurningOn(false);
-                    transitionTo(mOnState);
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
-                    break;
-
-                case ENABLED_READY:
-                    removeMessages(ENABLE_TIMEOUT);
-                    mPendingCommandState.setBleTurningOn(false);
-                    transitionTo(mBleOnState);
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
-                    break;
-
-                case SET_SCAN_MODE_TIMEOUT:
-                     warningLog("Timeout while setting scan mode. Continuing with disable...");
-                     //Fall through
-                case BEGIN_DISABLE:
-                    removeMessages(SET_SCAN_MODE_TIMEOUT);
-                    sendMessageDelayed(BREDR_STOP_TIMEOUT, BREDR_STOP_TIMEOUT_DELAY);
-                    adapterService.stopProfileServices();
-                    break;
-
-                case DISABLED:
-                    if (isTurningOn) {
-                        removeMessages(ENABLE_TIMEOUT);
-                        errorLog("Error enabling Bluetooth - hardware init failed?");
-                        mPendingCommandState.setTurningOn(false);
-                        transitionTo(mOffState);
-                        adapterService.stopProfileServices();
-                        notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
-                        break;
-                    }
-                    removeMessages(DISABLE_TIMEOUT);
-                    sendMessageDelayed(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
-                    if (adapterService.stopGattProfileService()) {
-                        debugLog("Stopping Gatt profile services that were post enabled");
-                        break;
-                    }
-                    //Fall through if no services or services already stopped
-                case BLE_STOPPED:
-                    removeMessages(BLE_STOP_TIMEOUT);
-                    setBleTurningOff(false);
-                    transitionTo(mOffState);
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
-                    break;
-
-                case BREDR_STOPPED:
-                    removeMessages(BREDR_STOP_TIMEOUT);
-                    setTurningOff(false);
-                    transitionTo(mBleOnState);
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
-                    break;
-
-                case BLE_START_TIMEOUT:
-                    errorLog("Error enabling Bluetooth (BLE start timeout)");
-                    mPendingCommandState.setBleTurningOn(false);
-                    transitionTo(mOffState);
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
-                    break;
-
-                case BREDR_START_TIMEOUT:
-                    errorLog("Error enabling Bluetooth (start timeout)");
-                    mPendingCommandState.setTurningOn(false);
-                    adapterService.stopProfileServices();
-                    transitionTo(mBleOnState);
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
-                    break;
-
-                case ENABLE_TIMEOUT:
-                    errorLog("Error enabling Bluetooth (enable timeout)");
-                    mPendingCommandState.setBleTurningOn(false);
-                    transitionTo(mOffState);
-                    adapterService.stopProfileServices();
-                    adapterService.stopGattProfileService();
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
-                    break;
-
-                case BREDR_STOP_TIMEOUT:
-                    errorLog("Error stopping Bluetooth profiles (stop timeout)");
-                    mPendingCommandState.setTurningOff(false);
-                    transitionTo(mBleOnState);
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
-                    break;
-
-                case BLE_STOP_TIMEOUT:
-                    errorLog("Error stopping Bluetooth profiles (BLE stop timeout)");
-                    mPendingCommandState.setTurningOff(false);
-                    transitionTo(mOffState);
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
-                    break;
-
-                case DISABLE_TIMEOUT:
-                    errorLog("Error disabling Bluetooth (disable timeout)");
-                    if (isTurningOn)
-                        mPendingCommandState.setTurningOn(false);
-                    adapterService.stopProfileServices();
-                    adapterService.stopGattProfileService();
-                    mPendingCommandState.setTurningOff(false);
-                    setBleTurningOff(false);
-                    transitionTo(mOffState);
-                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
+                    transitionTo(mTurningBleOnState);
                     break;
 
                 default:
+                    infoLog("Unhandled message - " + messageString(msg.what));
                     return false;
             }
             return true;
         }
+    }
 
-        private void logTransientStates() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("PendingCommand - transient state(s):");
+    private class BleOnState extends BaseAdapterState {
 
-            if (isTurningOn()) sb.append(" isTurningOn");
-            if (isTurningOff()) sb.append(" isTurningOff");
-            if (isBleTurningOn()) sb.append(" isBleTurningOn");
-            if (isBleTurningOff()) sb.append(" isBleTurningOff");
+        @Override
+        int getStateValue() {
+            return BluetoothAdapter.STATE_BLE_ON;
+        }
 
-            verboseLog(sb.toString());
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case USER_TURN_ON:
+                    transitionTo(mTurningOnState);
+                    break;
+
+                case BLE_TURN_OFF:
+                    transitionTo(mTurningBleOffState);
+                    break;
+
+                default:
+                    infoLog("Unhandled message - " + messageString(msg.what));
+                    return false;
+            }
+            return true;
         }
     }
 
-    private void notifyAdapterStateChange(int newState) {
-        AdapterService adapterService = mAdapterService;
-        AdapterProperties adapterProperties = mAdapterProperties;
-        if ((adapterService == null) || (adapterProperties == null)) {
-            errorLog("notifyAdapterStateChange after cleanup:" + newState);
-            return;
+    private class OnState extends BaseAdapterState {
+
+        @Override
+        int getStateValue() {
+            return BluetoothAdapter.STATE_ON;
         }
 
-        int oldState = adapterProperties.getState();
-        adapterProperties.setState(newState);
-        infoLog("Bluetooth adapter state changed: " + oldState + "-> " + newState);
-        adapterService.updateAdapterState(oldState, newState);
-    }
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case USER_TURN_OFF:
+                    transitionTo(mTurningOffState);
+                    break;
 
-    void stateChangeCallback(int status) {
-        if (status == AbstractionLayer.BT_STATE_OFF) {
-            sendMessage(DISABLED);
-
-        } else if (status == AbstractionLayer.BT_STATE_ON) {
-            // We should have got the property change for adapter and remote devices.
-            sendMessage(ENABLED_READY);
-
-        } else {
-            errorLog("Incorrect status in stateChangeCallback");
+                default:
+                    infoLog("Unhandled message - " + messageString(msg.what));
+                    return false;
+            }
+            return true;
         }
     }
 
-    private void infoLog(String msg) {
-        if (DBG) Log.i(TAG, msg);
+    private class TurningBleOnState extends BaseAdapterState {
+
+        @Override
+        int getStateValue() {
+            return BluetoothAdapter.STATE_BLE_TURNING_ON;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            sendMessageDelayed(BLE_START_TIMEOUT, BLE_START_TIMEOUT_DELAY);
+            mAdapterService.bringUpBle();
+        }
+
+        @Override
+        public void exit() {
+            removeMessages(BLE_START_TIMEOUT);
+            super.exit();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case BLE_STARTED:
+                    transitionTo(mBleOnState);
+                    break;
+
+                case BLE_START_TIMEOUT:
+                    errorLog(messageString(msg.what));
+                    transitionTo(mTurningBleOffState);
+                    break;
+
+                default:
+                    infoLog("Unhandled message - " + messageString(msg.what));
+                    return false;
+            }
+            return true;
+        }
     }
 
-    private void debugLog(String msg) {
-        if (DBG) Log.d(TAG, msg);
+    private class TurningOnState extends BaseAdapterState {
+
+        @Override
+        int getStateValue() {
+            return BluetoothAdapter.STATE_TURNING_ON;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            sendMessageDelayed(BREDR_START_TIMEOUT, BREDR_START_TIMEOUT_DELAY);
+            mAdapterService.startProfileServices();
+        }
+
+        @Override
+        public void exit() {
+            removeMessages(BREDR_START_TIMEOUT);
+            super.exit();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case BREDR_STARTED:
+                    transitionTo(mOnState);
+                    break;
+
+                case BREDR_START_TIMEOUT:
+                    errorLog(messageString(msg.what));
+                    transitionTo(mTurningOffState);
+                    break;
+
+                default:
+                    infoLog("Unhandled message - " + messageString(msg.what));
+                    return false;
+            }
+            return true;
+        }
     }
 
-    private void warningLog(String msg) {
-        if (DBG) Log.w(TAG, msg);
+    private class TurningOffState extends BaseAdapterState {
+
+        @Override
+        int getStateValue() {
+            return BluetoothAdapter.STATE_TURNING_OFF;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            sendMessageDelayed(BREDR_STOP_TIMEOUT, BREDR_STOP_TIMEOUT_DELAY);
+            mAdapterService.stopProfileServices();
+        }
+
+        @Override
+        public void exit() {
+            removeMessages(BREDR_STOP_TIMEOUT);
+            super.exit();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case BREDR_STOPPED:
+                    transitionTo(mBleOnState);
+                    break;
+
+                case BREDR_STOP_TIMEOUT:
+                    errorLog(messageString(msg.what));
+                    transitionTo(mTurningBleOffState);
+                    break;
+
+                default:
+                    infoLog("Unhandled message - " + messageString(msg.what));
+                    return false;
+            }
+            return true;
+        }
     }
 
-    private void verboseLog(String msg) {
-        if (VDBG) Log.v(TAG, msg);
-    }
+    private class TurningBleOffState extends BaseAdapterState {
 
-    private void errorLog(String msg) {
-        Log.e(TAG, msg);
-    }
+        @Override
+        int getStateValue() {
+            return BluetoothAdapter.STATE_BLE_TURNING_OFF;
+        }
 
+        @Override
+        public void enter() {
+            super.enter();
+            sendMessageDelayed(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
+            mAdapterService.bringDownBle();
+        }
+
+        @Override
+        public void exit() {
+            removeMessages(BLE_STOP_TIMEOUT);
+            super.exit();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case BLE_STOPPED:
+                    transitionTo(mOffState);
+                    break;
+
+                case BLE_STOP_TIMEOUT:
+                    errorLog(messageString(msg.what));
+                    transitionTo(mOffState);
+                    break;
+
+                default:
+                    infoLog("Unhandled message - " + messageString(msg.what));
+                    return false;
+            }
+            return true;
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index d7dab6d..81e5aae 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -18,24 +18,23 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothDevice;
-import com.android.bluetooth.a2dp.A2dpService;
-import com.android.bluetooth.hid.HidService;
-import com.android.bluetooth.hfp.HeadsetService;
-import com.android.bluetooth.a2dpsink.A2dpSinkService;
-import com.android.bluetooth.hfpclient.HeadsetClientService;
-import com.android.bluetooth.pbapclient.PbapClientService;
-
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.OobData;
 import android.content.Intent;
 import android.os.Message;
 import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
+import com.android.bluetooth.hid.HidHostService;
+import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
@@ -71,14 +70,15 @@
     private RemoteDevices mRemoteDevices;
     private BluetoothAdapter mAdapter;
 
-    private Set<BluetoothDevice> mPendingBondedDevices = new HashSet<>();
     private PendingCommandState mPendingCommandState = new PendingCommandState();
     private StableState mStableState = new StableState();
 
     public static final String OOBDATA = "oobdata";
 
-    private BondStateMachine(AdapterService service,
-            AdapterProperties prop, RemoteDevices remoteDevices) {
+    @VisibleForTesting Set<BluetoothDevice> mPendingBondedDevices = new HashSet<>();
+
+    private BondStateMachine(AdapterService service, AdapterProperties prop,
+            RemoteDevices remoteDevices) {
         super("BondStateMachine:");
         addState(mStableState);
         addState(mPendingCommandState);
@@ -89,8 +89,8 @@
         setInitialState(mStableState);
     }
 
-    public static BondStateMachine make(AdapterService service,
-            AdapterProperties prop, RemoteDevices remoteDevices) {
+    public static BondStateMachine make(AdapterService service, AdapterProperties prop,
+            RemoteDevices remoteDevices) {
         Log.d(TAG, "make");
         BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
         bsm.start();
@@ -101,12 +101,17 @@
         quitNow();
     }
 
-    public void cleanup() {
+    private void cleanup() {
         mAdapterService = null;
         mRemoteDevices = null;
         mAdapterProperties = null;
     }
 
+    @Override
+    protected void onQuitting() {
+        cleanup();
+    }
+
     private class StableState extends State {
         @Override
         public void enter() {
@@ -116,48 +121,44 @@
         @Override
         public boolean processMessage(Message msg) {
 
-            BluetoothDevice dev = (BluetoothDevice)msg.obj;
+            BluetoothDevice dev = (BluetoothDevice) msg.obj;
 
-            switch(msg.what) {
+            switch (msg.what) {
 
-              case CREATE_BOND:
-                  OobData oobData = null;
-                  if (msg.getData() != null)
-                      oobData = msg.getData().getParcelable(OOBDATA);
+                case CREATE_BOND:
+                    OobData oobData = null;
+                    if (msg.getData() != null) {
+                        oobData = msg.getData().getParcelable(OOBDATA);
+                    }
 
-                  createBond(dev, msg.arg1, oobData, true);
-                  break;
-              case REMOVE_BOND:
-                  removeBond(dev, true);
-                  break;
-              case BONDING_STATE_CHANGE:
-                int newState = msg.arg1;
+                    createBond(dev, msg.arg1, oobData, true);
+                    break;
+                case REMOVE_BOND:
+                    removeBond(dev, true);
+                    break;
+                case BONDING_STATE_CHANGE:
+                    int newState = msg.arg1;
                 /* if incoming pairing, transition to pending state */
-                if (newState == BluetoothDevice.BOND_BONDING)
-                {
-                    sendIntent(dev, newState, 0);
-                    transitionTo(mPendingCommandState);
-                }
-                else if (newState == BluetoothDevice.BOND_NONE)
-                {
+                    if (newState == BluetoothDevice.BOND_BONDING) {
+                        sendIntent(dev, newState, 0);
+                        transitionTo(mPendingCommandState);
+                    } else if (newState == BluetoothDevice.BOND_NONE) {
                     /* if the link key was deleted by the stack */
-                    sendIntent(dev, newState, 0);
-                }
-                else
-                {
-                    Log.e(TAG, "In stable state, received invalid newState: " + newState);
-                }
-                break;
-              case UUID_UPDATE:
-                  if (mPendingBondedDevices.contains(dev)) {
-                      sendIntent(dev, BluetoothDevice.BOND_BONDED, 0);
-                  }
-                  break;
-
-              case CANCEL_BOND:
-              default:
-                   Log.e(TAG, "Received unhandled state: " + msg.what);
-                   return false;
+                        sendIntent(dev, newState, 0);
+                    } else {
+                        Log.e(TAG, "In stable state, received invalid newState: "
+                                + state2str(newState));
+                    }
+                    break;
+                case UUID_UPDATE:
+                    if (mPendingBondedDevices.contains(dev)) {
+                        sendIntent(dev, BluetoothDevice.BOND_BONDED, 0);
+                    }
+                    break;
+                case CANCEL_BOND:
+                default:
+                    Log.e(TAG, "Received unhandled state: " + msg.what);
+                    return false;
             }
             return true;
         }
@@ -165,32 +166,32 @@
 
 
     private class PendingCommandState extends State {
-        private final ArrayList<BluetoothDevice> mDevices =
-            new ArrayList<BluetoothDevice>();
+        private final ArrayList<BluetoothDevice> mDevices = new ArrayList<BluetoothDevice>();
 
         @Override
         public void enter() {
             infoLog("Entering PendingCommandState State");
-            BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj;
+            BluetoothDevice dev = (BluetoothDevice) getCurrentMessage().obj;
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            BluetoothDevice dev = (BluetoothDevice)msg.obj;
+            BluetoothDevice dev = (BluetoothDevice) msg.obj;
             DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
             boolean result = false;
-             if (mDevices.contains(dev) && msg.what != CANCEL_BOND &&
-                   msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST &&
-                   msg.what != PIN_REQUEST) {
-                 deferMessage(msg);
-                 return true;
-             }
+            if (mDevices.contains(dev) && msg.what != CANCEL_BOND
+                    && msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST
+                    && msg.what != PIN_REQUEST) {
+                deferMessage(msg);
+                return true;
+            }
 
             switch (msg.what) {
                 case CREATE_BOND:
                     OobData oobData = null;
-                    if (msg.getData() != null)
+                    if (msg.getData() != null) {
                         oobData = msg.getData().getParcelable(OOBDATA);
+                    }
 
                     result = createBond(dev, msg.arg1, oobData, false);
                     break;
@@ -204,8 +205,7 @@
                     int newState = msg.arg1;
                     int reason = getUnbondReasonFromHALCode(msg.arg2);
                     sendIntent(dev, newState, reason);
-                    if(newState != BluetoothDevice.BOND_BONDING )
-                    {
+                    if (newState != BluetoothDevice.BOND_BONDING) {
                         /* this is either none/bonded, remove and transition */
                         result = !mDevices.remove(dev);
                         if (mDevices.isEmpty()) {
@@ -216,8 +216,7 @@
                             result = false;
                             transitionTo(mStableState);
                         }
-                        if (newState == BluetoothDevice.BOND_NONE)
-                        {
+                        if (newState == BluetoothDevice.BOND_NONE) {
                             mAdapterService.setPhonebookAccessPermission(dev,
                                     BluetoothDevice.ACCESS_UNKNOWN);
                             mAdapterService.setMessageAccessPermission(dev,
@@ -227,9 +226,9 @@
                             // Set the profile Priorities to undefined
                             clearProfilePriority(dev);
                         }
+                    } else if (!mDevices.contains(dev)) {
+                        result = true;
                     }
-                    else if(!mDevices.contains(dev))
-                        result=true;
                     break;
                 case SSP_REQUEST:
                     int passkey = msg.arg1;
@@ -239,8 +238,8 @@
                 case PIN_REQUEST:
                     BluetoothClass btClass = dev.getBluetoothClass();
                     int btDeviceClass = btClass.getDeviceClass();
-                    if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD ||
-                         btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
+                    if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || btDeviceClass
+                            == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
                         // Its a keyboard. Follow the HID spec recommendation of creating the
                         // passkey and displaying it to the user. If the keyboard doesn't follow
                         // the spec recommendation, check if the keyboard has a fixed PIN zero
@@ -248,9 +247,9 @@
                         //TODO: Maintain list of devices that have fixed pin
                         // Generate a variable 6-digit PIN in range of 100000-999999
                         // This is not truly random but good enough.
-                        int pin = 100000 + (int)Math.floor((Math.random() * (999999 - 100000)));
+                        int pin = 100000 + (int) Math.floor((Math.random() * (999999 - 100000)));
                         sendDisplayPinIntent(devProp.getAddress(), pin,
-                                 BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
+                                BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
                         break;
                     }
 
@@ -261,7 +260,7 @@
                         // In PIN_REQUEST, there is no passkey to display.So do not send the
                         // EXTRA_PAIRING_KEY type in the intent( 0 in SendDisplayPinIntent() )
                         sendDisplayPinIntent(devProp.getAddress(), 0,
-                                              BluetoothDevice.PAIRING_VARIANT_PIN);
+                                BluetoothDevice.PAIRING_VARIANT_PIN);
                     }
 
                     break;
@@ -269,7 +268,9 @@
                     Log.e(TAG, "Received unhandled event:" + msg.what);
                     return false;
             }
-            if (result) mDevices.add(dev);
+            if (result) {
+                mDevices.add(dev);
+            }
 
             return true;
         }
@@ -279,7 +280,7 @@
         if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
             if (!mAdapterService.cancelBondNative(addr)) {
-               Log.e(TAG, "Unexpected error while cancelling bond:");
+                Log.e(TAG, "Unexpected error while cancelling bond:");
             } else {
                 return true;
             }
@@ -291,9 +292,11 @@
         if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
             if (!mAdapterService.removeBondNative(addr)) {
-               Log.e(TAG, "Unexpected error while removing bond:");
+                Log.e(TAG, "Unexpected error while removing bond:");
             } else {
-                if (transition) transitionTo(mPendingCommandState);
+                if (transition) {
+                    transitionTo(mPendingCommandState);
+                }
                 return true;
             }
 
@@ -302,7 +305,7 @@
     }
 
     private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
-                               boolean transition) {
+            boolean transition) {
         if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
             infoLog("Bond address is:" + dev);
             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
@@ -314,8 +317,7 @@
             }
 
             if (!result) {
-                sendIntent(dev, BluetoothDevice.BOND_NONE,
-                           BluetoothDevice.UNBOND_REASON_REMOVED);
+                sendIntent(dev, BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED);
                 return false;
             } else if (transition) {
                 transitionTo(mPendingCommandState);
@@ -338,7 +340,8 @@
         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
     }
 
-    private void sendIntent(BluetoothDevice device, int newState, int reason) {
+    @VisibleForTesting
+    void sendIntent(BluetoothDevice device, int newState, int reason) {
         DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
         int oldState = BluetoothDevice.BOND_NONE;
         if (newState != BluetoothDevice.BOND_NONE
@@ -362,6 +365,9 @@
                 throw new IllegalArgumentException("Invalid old state " + oldState);
             }
         }
+        if (oldState == newState) {
+            return;
+        }
 
         mAdapterProperties.onBondStateChanged(device, newState);
 
@@ -384,12 +390,12 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
         intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
-        if (newState == BluetoothDevice.BOND_NONE)
+        if (newState == BluetoothDevice.BOND_NONE) {
             intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
-        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
-                AdapterService.BLUETOOTH_PERM);
-        infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
-                + " NewState: " + newState);
+        }
+        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
+        infoLog("Bond State Change Intent:" + device + " " + state2str(oldState) + " => "
+                + state2str(newState));
     }
 
     void bondStateChangeCallback(int status, byte[] address, int newState) {
@@ -402,53 +408,53 @@
             device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
         }
 
-        infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
-                + " newState: " + newState);
+        infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device + " newState: "
+                + newState);
 
         Message msg = obtainMessage(BONDING_STATE_CHANGE);
         msg.obj = device;
 
-        if (newState == BOND_STATE_BONDED)
+        if (newState == BOND_STATE_BONDED) {
             msg.arg1 = BluetoothDevice.BOND_BONDED;
-        else if (newState == BOND_STATE_BONDING)
+        } else if (newState == BOND_STATE_BONDING) {
             msg.arg1 = BluetoothDevice.BOND_BONDING;
-        else
+        } else {
             msg.arg1 = BluetoothDevice.BOND_NONE;
+        }
         msg.arg2 = status;
 
         sendMessage(msg);
     }
 
-    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
-            int passkey) {
+    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, int passkey) {
         //TODO(BT): Get wakelock and update name and cod
         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
         if (bdDevice == null) {
             mRemoteDevices.addDeviceProperties(address);
         }
-        infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " +
-                cod + " pairingVariant " + pairingVariant + " passkey: " + passkey);
+        infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " + cod
+                + " pairingVariant " + pairingVariant + " passkey: " + passkey);
         int variant;
         boolean displayPasskey = false;
-        switch(pairingVariant) {
+        switch (pairingVariant) {
 
-            case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION :
+            case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION:
                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION;
                 displayPasskey = true;
-            break;
+                break;
 
-            case AbstractionLayer.BT_SSP_VARIANT_CONSENT :
+            case AbstractionLayer.BT_SSP_VARIANT_CONSENT:
                 variant = BluetoothDevice.PAIRING_VARIANT_CONSENT;
-            break;
+                break;
 
-            case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY :
+            case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY:
                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY;
-            break;
+                break;
 
-            case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION :
+            case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION:
                 variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY;
                 displayPasskey = true;
-            break;
+                break;
 
             default:
                 errorLog("SSP Pairing variant not present");
@@ -456,15 +462,16 @@
         }
         BluetoothDevice device = mRemoteDevices.getDevice(address);
         if (device == null) {
-           warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
-           mRemoteDevices.addDeviceProperties(address);
-           device = mRemoteDevices.getDevice(address);
+            warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
+            mRemoteDevices.addDeviceProperties(address);
+            device = mRemoteDevices.getDevice(address);
         }
 
         Message msg = obtainMessage(SSP_REQUEST);
         msg.obj = device;
-        if(displayPasskey)
+        if (displayPasskey) {
             msg.arg1 = passkey;
+        }
         msg.arg2 = variant;
         sendMessage(msg);
     }
@@ -476,8 +483,7 @@
         if (bdDevice == null) {
             mRemoteDevices.addDeviceProperties(address);
         }
-        infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" +
-                cod);
+        infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" + cod);
 
         Message msg = obtainMessage(PIN_REQUEST);
         msg.obj = bdDevice;
@@ -487,29 +493,46 @@
     }
 
     private void clearProfilePriority(BluetoothDevice device) {
-        HidService hidService = HidService.getHidService();
+        HidHostService hidService = HidHostService.getHidHostService();
         A2dpService a2dpService = A2dpService.getA2dpService();
         HeadsetService headsetService = HeadsetService.getHeadsetService();
         HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
         A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
         PbapClientService pbapClientService = PbapClientService.getPbapClientService();
 
-        if (hidService != null)
+        if (hidService != null) {
             hidService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
-        if (a2dpService != null)
+        }
+        if (a2dpService != null) {
             a2dpService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
-        if (headsetService != null)
+        }
+        if (headsetService != null) {
             headsetService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
-        if (headsetClientService != null)
+        }
+        if (headsetClientService != null) {
             headsetClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
-        if (a2dpSinkService != null)
+        }
+        if (a2dpSinkService != null) {
             a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
-        if (pbapClientService != null)
+        }
+        if (pbapClientService != null) {
             pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+        }
 
         // Clear Absolute Volume black list
-        if(a2dpService != null)
+        if (a2dpService != null) {
             a2dpService.resetAvrcpBlacklist(device);
+        }
+    }
+
+    private String state2str(int state) {
+        if (state == BluetoothDevice.BOND_NONE) {
+            return "BOND_NONE";
+        } else if (state == BluetoothDevice.BOND_BONDING) {
+            return "BOND_BONDING";
+        } else if (state == BluetoothDevice.BOND_BONDED) {
+            return "BOND_BONDED";
+        } else return "UNKNOWN(" + state + ")";
     }
 
     private void infoLog(String msg) {
@@ -524,17 +547,18 @@
         Log.w(TAG, msg);
     }
 
-    private int getUnbondReasonFromHALCode (int reason) {
-        if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
+    private int getUnbondReasonFromHALCode(int reason) {
+        if (reason == AbstractionLayer.BT_STATUS_SUCCESS) {
             return BluetoothDevice.BOND_SUCCESS;
-        else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
+        } else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN) {
             return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
-        else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
+        } else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE) {
             return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
-        else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
+        } else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED) {
             return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
-        else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
+        } else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT) {
             return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
+        }
 
         /* default */
         return BluetoothDevice.UNBOND_REASON_REMOVED;
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index e55601d..8a9c0a1 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -16,61 +16,95 @@
 
 package com.android.bluetooth.btservice;
 
-import java.util.ArrayList;
-
 import android.bluetooth.BluetoothProfile;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.provider.Settings;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.a2dpsink.A2dpSinkService;
+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;
-import com.android.bluetooth.hid.HidService;
-import com.android.bluetooth.pan.PanService;
-import com.android.bluetooth.gatt.GattService;
+import com.android.bluetooth.hid.HidDeviceService;
+import com.android.bluetooth.hid.HidHostService;
 import com.android.bluetooth.map.BluetoothMapService;
 import com.android.bluetooth.mapclient.MapClientService;
-import com.android.bluetooth.sap.SapService;
-import com.android.bluetooth.pbapclient.PbapClientService;
-import com.android.bluetooth.hid.HidDevService;
-import com.android.bluetooth.pbap.BluetoothPbapService;
 import com.android.bluetooth.opp.BluetoothOppService;
+import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.pbap.BluetoothPbapService;
+import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.bluetooth.sap.SapService;
+
+import java.util.ArrayList;
 
 public class Config {
     private static final String TAG = "AdapterServiceConfig";
-    /**
-     * List of profile services.
-     */
-    @SuppressWarnings("rawtypes")
-    // Do not inclue OPP and PBAP, because their services
-    // are not managed by AdapterService
-    private static final Class[] PROFILE_SERVICES = {HeadsetService.class, A2dpService.class,
-            A2dpSinkService.class, HidService.class, HealthService.class, PanService.class,
-            GattService.class, BluetoothMapService.class, HeadsetClientService.class,
-            AvrcpControllerService.class, SapService.class, PbapClientService.class,
-            MapClientService.class, HidDevService.class, BluetoothOppService.class,
-            BluetoothPbapService.class};
-    /**
-     * Resource flag to indicate whether profile is supported or not.
-     */
-    private static final int[] PROFILE_SERVICES_FLAG = {R.bool.profile_supported_hs_hfp,
-            R.bool.profile_supported_a2dp, R.bool.profile_supported_a2dp_sink,
-            R.bool.profile_supported_hid, R.bool.profile_supported_hdp,
-            R.bool.profile_supported_pan, R.bool.profile_supported_gatt,
-            R.bool.profile_supported_map, R.bool.profile_supported_hfpclient,
-            R.bool.profile_supported_avrcp_controller, R.bool.profile_supported_sap,
-            R.bool.profile_supported_pbapclient, R.bool.profile_supported_mapmce,
-            R.bool.profile_supported_hidd, R.bool.profile_supported_opp,
-            R.bool.profile_supported_pbap};
 
-    private static Class[] SUPPORTED_PROFILES = new Class[0];
+    private static class ProfileConfig {
+        Class mClass;
+        int mSupported;
+        long mMask;
+
+        ProfileConfig(Class theClass, int supportedFlag, long mask) {
+            mClass = theClass;
+            mSupported = supportedFlag;
+            mMask = mask;
+        }
+    }
+
+    /**
+     * List of profile services with the profile-supported resource flag and bit mask.
+     */
+    private static final ProfileConfig[] PROFILE_SERVICES_AND_FLAGS = {
+            new ProfileConfig(HeadsetService.class, R.bool.profile_supported_hs_hfp,
+                    (1 << BluetoothProfile.HEADSET)),
+            new ProfileConfig(A2dpService.class, R.bool.profile_supported_a2dp,
+                    (1 << BluetoothProfile.A2DP)),
+            new ProfileConfig(A2dpSinkService.class, R.bool.profile_supported_a2dp_sink,
+                    (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,
+                    (1 << BluetoothProfile.GATT)),
+            new ProfileConfig(BluetoothMapService.class, R.bool.profile_supported_map,
+                    (1 << BluetoothProfile.MAP)),
+            new ProfileConfig(HeadsetClientService.class, R.bool.profile_supported_hfpclient,
+                    (1 << BluetoothProfile.HEADSET_CLIENT)),
+            new ProfileConfig(AvrcpTargetService.class, R.bool.profile_supported_avrcp_target,
+                    (1 << BluetoothProfile.AVRCP)),
+            new ProfileConfig(AvrcpControllerService.class,
+                    R.bool.profile_supported_avrcp_controller,
+                    (1 << BluetoothProfile.AVRCP_CONTROLLER)),
+            new ProfileConfig(SapService.class, R.bool.profile_supported_sap,
+                    (1 << BluetoothProfile.SAP)),
+            new ProfileConfig(PbapClientService.class, R.bool.profile_supported_pbapclient,
+                    (1 << BluetoothProfile.PBAP_CLIENT)),
+            new ProfileConfig(MapClientService.class, R.bool.profile_supported_mapmce,
+                    (1 << BluetoothProfile.MAP_CLIENT)),
+            new ProfileConfig(HidDeviceService.class, R.bool.profile_supported_hid_device,
+                    (1 << BluetoothProfile.HID_DEVICE)),
+            new ProfileConfig(BluetoothOppService.class, R.bool.profile_supported_opp,
+                    (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,
+                    (1 << BluetoothProfile.HEARING_AID))
+    };
+
+    private static Class[] sSupportedProfiles = new Class[0];
 
     static void init(Context ctx) {
         if (ctx == null) {
@@ -81,83 +115,51 @@
             return;
         }
 
-        ArrayList<Class> profiles = new ArrayList<Class>(PROFILE_SERVICES.length);
-        for (int i=0; i < PROFILE_SERVICES_FLAG.length; i++) {
-            boolean supported = resources.getBoolean(PROFILE_SERVICES_FLAG[i]);
-            if (supported && !isProfileDisabled(ctx, PROFILE_SERVICES[i])) {
-                Log.d(TAG, "Adding " + PROFILE_SERVICES[i].getSimpleName());
-                profiles.add(PROFILE_SERVICES[i]);
+        ArrayList<Class> profiles = new ArrayList<>(PROFILE_SERVICES_AND_FLAGS.length);
+        for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
+            boolean supported = resources.getBoolean(config.mSupported);
+
+            if (!supported && (config.mClass == HearingAidService.class) && FeatureFlagUtils
+                                .isEnabled(ctx, FeatureFlagUtils.HEARING_AID_SETTINGS)) {
+                Log.v(TAG, "Feature Flag enables support for HearingAidService");
+                supported = true;
             }
+
+            if (supported && !isProfileDisabled(ctx, config.mMask)) {
+                Log.v(TAG, "Adding " + config.mClass.getSimpleName());
+                profiles.add(config.mClass);
+            }
+            sSupportedProfiles = profiles.toArray(new Class[profiles.size()]);
         }
-        SUPPORTED_PROFILES = profiles.toArray(new Class[profiles.size()]);
     }
 
-    static Class[]  getSupportedProfiles() {
-        return SUPPORTED_PROFILES;
+    static Class[] getSupportedProfiles() {
+        return sSupportedProfiles;
+    }
+
+    private static long getProfileMask(Class profile) {
+        for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
+            if (config.mClass == profile) {
+                return config.mMask;
+            }
+        }
+        Log.w(TAG, "Could not find profile bit mask for " + profile.getSimpleName());
+        return 0;
     }
 
     static long getSupportedProfilesBitMask() {
         long mask = 0;
         for (final Class profileClass : getSupportedProfiles()) {
-            final int profileIndex = getProfileIndex(profileClass);
-
-            if (profileIndex != -1) {
-                mask |= 1 << getProfileIndex(profileClass);
-            }
+            mask |= getProfileMask(profileClass);
         }
-
         return mask;
     }
 
-    private static boolean isProfileDisabled(Context context, Class profile) {
-        final int profileIndex = getProfileIndex(profile);
-
-        if (profileIndex == -1) {
-            Log.w(TAG, "Could not find profile bit mask");
-            return false;
-        }
-
+    private static boolean isProfileDisabled(Context context, long profileMask) {
         final ContentResolver resolver = context.getContentResolver();
-        final long disabledProfilesBitMask = Settings.Global.getLong(resolver,
-                Settings.Global.BLUETOOTH_DISABLED_PROFILES, 0);
-        final long profileBit = 1 << profileIndex;
+        final long disabledProfilesBitMask =
+                Settings.Global.getLong(resolver, Settings.Global.BLUETOOTH_DISABLED_PROFILES, 0);
 
-        return (disabledProfilesBitMask & profileBit) != 0;
-    }
-
-    private static int getProfileIndex(Class profile) {
-        int profileIndex = -1;
-
-        if (profile == HeadsetService.class) {
-            profileIndex = BluetoothProfile.HEADSET;
-        } else if (profile == A2dpService.class) {
-            profileIndex = BluetoothProfile.A2DP;
-        } else if (profile == A2dpSinkService.class) {
-            profileIndex = BluetoothProfile.A2DP_SINK;
-        } else if (profile == HidService.class) {
-            profileIndex = BluetoothProfile.INPUT_DEVICE;
-        } else if (profile == HealthService.class) {
-            profileIndex = BluetoothProfile.HEALTH;
-        } else if (profile == PanService.class) {
-            profileIndex = BluetoothProfile.PAN;
-        } else if (profile == GattService.class) {
-            profileIndex = BluetoothProfile.GATT;
-        } else if (profile == BluetoothMapService.class) {
-            profileIndex = BluetoothProfile.MAP;
-        } else if (profile == HeadsetClientService.class) {
-            profileIndex = BluetoothProfile.HEADSET_CLIENT;
-        } else if (profile == AvrcpControllerService.class) {
-            profileIndex = BluetoothProfile.AVRCP_CONTROLLER;
-        } else if (profile == SapService.class) {
-            profileIndex = BluetoothProfile.SAP;
-        } else if (profile == PbapClientService.class) {
-            profileIndex = BluetoothProfile.PBAP_CLIENT;
-        } else if (profile == MapClientService.class) {
-            profileIndex = BluetoothProfile.MAP_CLIENT;
-        } else if (profile == HidDevService.class) {
-            profileIndex = BluetoothProfile.INPUT_HOST;
-        }
-
-        return profileIndex;
+        return (disabledProfilesBitMask & profileMask) != 0;
     }
 }
diff --git a/src/com/android/bluetooth/btservice/JniCallbacks.java b/src/com/android/bluetooth/btservice/JniCallbacks.java
index 4be0228..c835999 100644
--- a/src/com/android/bluetooth/btservice/JniCallbacks.java
+++ b/src/com/android/bluetooth/btservice/JniCallbacks.java
@@ -20,11 +20,11 @@
 
     private RemoteDevices mRemoteDevices;
     private AdapterProperties mAdapterProperties;
-    private AdapterState mAdapterStateMachine;
+    private AdapterService mAdapterService;
     private BondStateMachine mBondStateMachine;
 
-    JniCallbacks(AdapterState adapterStateMachine,AdapterProperties adapterProperties) {
-        mAdapterStateMachine = adapterStateMachine;
+    JniCallbacks(AdapterService adapterService, AdapterProperties adapterProperties) {
+        mAdapterService = adapterService;
         mAdapterProperties = adapterProperties;
     }
 
@@ -36,7 +36,7 @@
     void cleanup() {
         mRemoteDevices = null;
         mAdapterProperties = null;
-        mAdapterStateMachine = null;
+        mAdapterService = null;
         mBondStateMachine = null;
     }
 
@@ -45,11 +45,10 @@
         throw new CloneNotSupportedException();
     }
 
-    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
-            int passkey) {
-        mBondStateMachine.sspRequestCallback(address, name, cod, pairingVariant,
-            passkey);
+    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, int passkey) {
+        mBondStateMachine.sspRequestCallback(address, name, cod, pairingVariant, passkey);
     }
+
     void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] val) {
         mRemoteDevices.devicePropertyChangedCallback(address, types, val);
     }
@@ -71,7 +70,7 @@
     }
 
     void stateChangeCallback(int status) {
-        mAdapterStateMachine.stateChangeCallback(status);
+        mAdapterService.stateChangeCallback(status);
     }
 
     void discoveryStateChangeCallback(int state) {
diff --git a/src/com/android/bluetooth/btservice/MetricsLogger.java b/src/com/android/bluetooth/btservice/MetricsLogger.java
new file mode 100644
index 0000000..b8a3d18
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/MetricsLogger.java
@@ -0,0 +1,60 @@
+/*
+ * 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.btservice;
+
+import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog;
+import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
+import com.android.bluetooth.BluetoothMetricsProto.ProfileId;
+
+import java.util.HashMap;
+
+/**
+ * Class with static methods for logging metrics data
+ */
+public class MetricsLogger {
+    private static final HashMap<ProfileId, Integer> sProfileConnectionCounts = new HashMap<>();
+
+    /**
+     * Log profile connection event by incrementing an internal counter for that profile.
+     * This log persists over adapter enable/disable and only get cleared when metrics are
+     * dumped or when Bluetooth process is killed.
+     *
+     * @param profileId Bluetooth profile that is connected at this event
+     */
+    public static void logProfileConnectionEvent(ProfileId profileId) {
+        synchronized (sProfileConnectionCounts) {
+            sProfileConnectionCounts.merge(profileId, 1, Integer::sum);
+        }
+    }
+
+    /**
+     * Dump collected metrics into proto using a builder.
+     * Clean up internal data after the dump.
+     *
+     * @param metricsBuilder proto builder for {@link BluetoothLog}
+     */
+    public static void dumpProto(BluetoothLog.Builder metricsBuilder) {
+        synchronized (sProfileConnectionCounts) {
+            sProfileConnectionCounts.forEach(
+                    (key, value) -> metricsBuilder.addProfileConnectionStats(
+                            ProfileConnectionStats.newBuilder()
+                                    .setProfileId(key)
+                                    .setNumTimesConnected(value)
+                                    .build()));
+            sProfileConnectionCounts.clear();
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/btservice/PhonePolicy.java b/src/com/android/bluetooth/btservice/PhonePolicy.java
index b3ef04b..a0ad490 100644
--- a/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -21,9 +21,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothSap;
 import android.bluetooth.BluetoothUuid;
-import android.bluetooth.IBluetooth;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -31,13 +29,15 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Parcelable;
 import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
 import com.android.bluetooth.a2dp.A2dpService;
-import com.android.bluetooth.hid.HidService;
+import com.android.bluetooth.hearingaid.HearingAidService;
 import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hid.HidHostService;
 import com.android.bluetooth.pan.PanService;
 import com.android.internal.R;
 
@@ -70,23 +70,25 @@
 // will try to connect other profiles on the same device. This is to avoid collision if devices
 // somehow end up trying to connect at same time or general connection issues.
 class PhonePolicy {
-    final private static boolean DBG = true;
-    final private static String TAG = "BluetoothPhonePolicy";
+    private static final boolean DBG = true;
+    private static final String TAG = "BluetoothPhonePolicy";
 
     // Message types for the handler (internal messages generated by intents or timeouts)
-    final private static int MESSAGE_PROFILE_CONNECTION_STATE_CHANGED = 1;
-    final private static int MESSAGE_PROFILE_INIT_PRIORITIES = 2;
-    final private static int MESSAGE_CONNECT_OTHER_PROFILES = 3;
-    final private static int MESSAGE_ADAPTER_STATE_TURNED_ON = 4;
+    private static final int MESSAGE_PROFILE_CONNECTION_STATE_CHANGED = 1;
+    private static final int MESSAGE_PROFILE_INIT_PRIORITIES = 2;
+    private static final int MESSAGE_CONNECT_OTHER_PROFILES = 3;
+    private static final int MESSAGE_ADAPTER_STATE_TURNED_ON = 4;
+    private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
 
     // Timeouts
-    final private static int CONNECT_OTHER_PROFILES_TIMEOUT = 6000; // 6s
+    @VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s
 
-    final private AdapterService mAdapterService;
-    final private ServiceFactory mFactory;
-    final private Handler mHandler;
-    final private HashSet<BluetoothDevice> mHeadsetRetrySet = new HashSet<>();
-    final private HashSet<BluetoothDevice> mA2dpRetrySet = new HashSet<>();
+    private final AdapterService mAdapterService;
+    private final ServiceFactory mFactory;
+    private final Handler mHandler;
+    private final HashSet<BluetoothDevice> mHeadsetRetrySet = new HashSet<>();
+    private final HashSet<BluetoothDevice> mA2dpRetrySet = new HashSet<>();
+    private final HashSet<BluetoothDevice> mConnectOtherProfilesDeviceSet = new HashSet<>();
 
     // Broadcast receiver for all changes to states of various profiles
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -100,17 +102,18 @@
             switch (action) {
                 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
                     mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
-                                    BluetoothProfile.HEADSET,
-                                    -1, // No-op argument
-                                    intent)
-                            .sendToTarget();
+                            BluetoothProfile.HEADSET, -1, // No-op argument
+                            intent).sendToTarget();
                     break;
                 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
                     mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
-                                    BluetoothProfile.A2DP,
-                                    -1, // No-op argument
-                                    intent)
-                            .sendToTarget();
+                            BluetoothProfile.A2DP, -1, // No-op argument
+                            intent).sendToTarget();
+                    break;
+                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
+                            BluetoothProfile.A2DP, -1, // No-op argument
+                            intent).sendToTarget();
                     break;
                 case BluetoothAdapter.ACTION_STATE_CHANGED:
                     // Only pass the message on if the adapter has actually changed state from
@@ -130,8 +133,8 @@
         }
     };
 
-    // ONLY for testing
-    public BroadcastReceiver getBroadcastReceiver() {
+    @VisibleForTesting
+    BroadcastReceiver getBroadcastReceiver() {
         return mReceiver;
     }
 
@@ -158,7 +161,8 @@
                         }
                         processInitProfilePriorities(device, uuidsToSend);
                     }
-                } break;
+                }
+                break;
 
                 case MESSAGE_PROFILE_CONNECTION_STATE_CHANGED: {
                     Intent intent = (Intent) msg.obj;
@@ -167,14 +171,25 @@
                     int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
                     int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
                     processProfileStateChanged(device, msg.arg1, nextState, prevState);
-                } break;
+                }
+                break;
 
-                case MESSAGE_CONNECT_OTHER_PROFILES:
+                case MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED: {
+                    Intent intent = (Intent) msg.obj;
+                    BluetoothDevice activeDevice =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    processProfileActiveDeviceChanged(activeDevice, msg.arg1);
+                }
+                break;
+
+                case MESSAGE_CONNECT_OTHER_PROFILES: {
                     // Called when we try connect some profiles in processConnectOtherProfiles but
                     // we send a delayed message to try connecting the remaining profiles
-                    processConnectOtherProfiles((BluetoothDevice) msg.obj);
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    processConnectOtherProfiles(device);
+                    mConnectOtherProfilesDeviceSet.remove(device);
                     break;
-
+                }
                 case MESSAGE_ADAPTER_STATE_TURNED_ON:
                     // Call auto connect when adapter switches state to ON
                     resetStates();
@@ -182,7 +197,9 @@
                     break;
             }
         }
-    };
+    }
+
+    ;
 
     // Policy API functions for lifecycle management (protected)
     protected void start() {
@@ -191,8 +208,10 @@
         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothDevice.ACTION_UUID);
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
         mAdapterService.registerReceiver(mReceiver, filter);
     }
+
     protected void cleanup() {
         mAdapterService.unregisterReceiver(mReceiver);
         resetStates();
@@ -207,62 +226,103 @@
     // Policy implementation, all functions MUST be private
     private void processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids) {
         debugLog("processInitProfilePriorities() - device " + device);
-        HidService hidService = mFactory.getHidService();
+        HidHostService hidService = mFactory.getHidHostService();
         A2dpService a2dpService = mFactory.getA2dpService();
         HeadsetService headsetService = mFactory.getHeadsetService();
         PanService panService = mFactory.getPanService();
+        HearingAidService hearingAidService = mFactory.getHearingAidService();
 
         // Set profile priorities only for the profiles discovered on the remote device.
         // This avoids needless auto-connect attempts to profiles non-existent on the remote device
-        if ((hidService != null)
-                && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid)
-                           || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp))
-                && (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
+        if ((hidService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid)
+                || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) && (
+                hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
             hidService.setPriority(device, BluetoothProfile.PRIORITY_ON);
         }
 
         // If we do not have a stored priority for HFP/A2DP (all roles) then default to on.
-        if ((headsetService != null)
-                && ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)
-                            || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))
-                           && (headsetService.getPriority(device)
-                                      == BluetoothProfile.PRIORITY_UNDEFINED))) {
+        if ((headsetService != null) && ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)
+                || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) && (
+                headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))) {
             headsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
         }
 
-        if ((a2dpService != null)
-                && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)
-                           || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist))
-                && (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
+        if ((a2dpService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)
+                || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist)) && (
+                a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
             a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
         }
 
-        if ((panService != null)
-                && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU)
-                           && (panService.getPriority(device)
-                                      == BluetoothProfile.PRIORITY_UNDEFINED)
-                           && mAdapterService.getResources().getBoolean(
-                                      R.bool.config_bluetooth_pan_enable_autoconnect))) {
+        if ((panService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU) && (
+                panService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)
+                && mAdapterService.getResources()
+                .getBoolean(R.bool.config_bluetooth_pan_enable_autoconnect))) {
             panService.setPriority(device, BluetoothProfile.PRIORITY_ON);
         }
+
+        if ((hearingAidService != null) && BluetoothUuid.isUuidPresent(uuids,
+                BluetoothUuid.HearingAid) && (hearingAidService.getPriority(device)
+                == BluetoothProfile.PRIORITY_UNDEFINED)) {
+            debugLog("setting hearing aid profile priority for device " + device);
+            hearingAidService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        }
     }
 
-    private void processProfileStateChanged(
-            BluetoothDevice device, int profileId, int nextState, int prevState) {
+    private void processProfileStateChanged(BluetoothDevice device, int profileId, int nextState,
+            int prevState) {
         debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", "
                 + prevState + " -> " + nextState);
-        if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET))
-                && (nextState == BluetoothProfile.STATE_CONNECTED)) {
-            switch (profileId) {
-                case BluetoothProfile.A2DP:
-                    mA2dpRetrySet.remove(device);
-                    break;
-                case BluetoothProfile.HEADSET:
-                    mHeadsetRetrySet.remove(device);
-                    break;
+        if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET))) {
+            if (nextState == BluetoothProfile.STATE_CONNECTED) {
+                switch (profileId) {
+                    case BluetoothProfile.A2DP:
+                        mA2dpRetrySet.remove(device);
+                        break;
+                    case BluetoothProfile.HEADSET:
+                        mHeadsetRetrySet.remove(device);
+                        break;
+                }
+                connectOtherProfile(device);
             }
-            connectOtherProfile(device);
-            setProfileAutoConnectionPriority(device, profileId);
+            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);
+                }
+            }
+        }
+    }
+
+    private void processProfileActiveDeviceChanged(BluetoothDevice activeDevice, int profileId) {
+        debugLog("processProfileActiveDeviceChanged, activeDevice=" + activeDevice + ", profile="
+                + profileId);
+        switch (profileId) {
+            // Tracking active device changed intent only for A2DP so that we always connect to a
+            // single device after toggling Bluetooth
+            case BluetoothProfile.A2DP:
+                // Ignore null active device since we don't know if the change is triggered by
+                // normal device disconnection during Bluetooth shutdown or user action
+                if (activeDevice == null) {
+                    warnLog("processProfileActiveDeviceChanged: ignore null A2DP active device");
+                    return;
+                }
+                for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+                    removeAutoConnectFromA2dpSink(device);
+                    removeAutoConnectFromHeadset(device);
+                }
+                setAutoConnectForA2dpSink(activeDevice);
+                setAutoConnectForHeadset(activeDevice);
+                break;
         }
     }
 
@@ -273,67 +333,71 @@
 
     private void autoConnect() {
         if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
-            errorLog("autoConnect() - BT is not ON. Exiting autoConnect");
+            errorLog("autoConnect: BT is not ON. Exiting autoConnect");
             return;
         }
 
         if (!mAdapterService.isQuietModeEnabled()) {
-            debugLog("autoConnect() - Initiate auto connection on BT on...");
-            // Phone profiles.
-            autoConnectHeadset();
-            autoConnectA2dp();
+            debugLog("autoConnect: Initiate auto connection on BT on...");
+            final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+            if (bondedDevices == null) {
+                errorLog("autoConnect: bondedDevices are null");
+                return;
+            }
+            for (BluetoothDevice device : bondedDevices) {
+                autoConnectHeadset(device);
+                autoConnectA2dp(device);
+            }
         } else {
             debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections");
         }
     }
 
-    private void autoConnectHeadset() {
-        final HeadsetService hsService = mFactory.getHeadsetService();
-        if (hsService == null) {
-            errorLog("autoConnectHeadset, service is null");
+    private void autoConnectA2dp(BluetoothDevice device) {
+        final A2dpService a2dpService = mFactory.getA2dpService();
+        if (a2dpService == null) {
+            warnLog("autoConnectA2dp: service is null, failed to connect to " + device);
             return;
         }
-        final BluetoothDevice bondedDevices[] = mAdapterService.getBondedDevices();
-        if (bondedDevices == null) {
-            errorLog("autoConnectHeadset, bondedDevices are null");
-            return;
-        }
-        for (BluetoothDevice device : bondedDevices) {
-            debugLog("autoConnectHeadset, attempt auto-connect with device " + device);
-            if (hsService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
-                debugLog("autoConnectHeadset, Connecting HFP with " + device);
-                hsService.connect(device);
-            }
+        int a2dpPriority = a2dpService.getPriority(device);
+        if (a2dpPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+            debugLog("autoConnectA2dp: connecting A2DP with " + device);
+            a2dpService.connect(device);
+        } else {
+            debugLog("autoConnectA2dp: skipped auto-connect A2DP with device " + device
+                    + " priority " + a2dpPriority);
         }
     }
 
-    private void autoConnectA2dp() {
-        final A2dpService a2dpService = mFactory.getA2dpService();
-        if (a2dpService == null) {
-            errorLog("autoConnectA2dp, service is null");
+    private void autoConnectHeadset(BluetoothDevice device) {
+        final HeadsetService hsService = mFactory.getHeadsetService();
+        if (hsService == null) {
+            warnLog("autoConnectHeadset: service is null, failed to connect to " + device);
             return;
         }
-        final BluetoothDevice bondedDevices[] = mAdapterService.getBondedDevices();
-        if (bondedDevices == null) {
-            errorLog("autoConnectA2dp, bondedDevices are null");
-            return;
-        }
-        for (BluetoothDevice device : bondedDevices) {
-            debugLog("autoConnectA2dp, attempt auto-connect with device " + device);
-            if (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
-                debugLog("autoConnectA2dp, connecting A2DP with " + device);
-                a2dpService.connect(device);
-            }
+        int headsetPriority = hsService.getPriority(device);
+        if (headsetPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+            debugLog("autoConnectHeadset: Connecting HFP with " + device);
+            hsService.connect(device);
+        } else {
+            debugLog("autoConnectHeadset: skipped auto-connect HFP with device " + device
+                    + " priority " + headsetPriority);
         }
     }
 
     private void connectOtherProfile(BluetoothDevice device) {
-        if ((!mHandler.hasMessages(MESSAGE_CONNECT_OTHER_PROFILES))
-                && (!mAdapterService.isQuietModeEnabled())) {
-            Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES);
-            m.obj = device;
-            mHandler.sendMessageDelayed(m, CONNECT_OTHER_PROFILES_TIMEOUT);
+        if (mAdapterService.isQuietModeEnabled()) {
+            debugLog("connectOtherProfile: in quiet mode, skip connect other profile " + device);
+            return;
         }
+        if (mConnectOtherProfilesDeviceSet.contains(device)) {
+            debugLog("connectOtherProfile: already scheduled callback for " + device);
+            return;
+        }
+        mConnectOtherProfilesDeviceSet.add(device);
+        Message m = mHandler.obtainMessage(MESSAGE_CONNECT_OTHER_PROFILES);
+        m.obj = device;
+        mHandler.sendMessageDelayed(m, sConnectOtherProfilesTimeoutMillis);
     }
 
     // This function is called whenever a profile is connected.  This allows any other bluetooth
@@ -350,6 +414,7 @@
         A2dpService a2dpService = mFactory.getA2dpService();
         PanService panService = mFactory.getPanService();
 
+        boolean atLeastOneProfileConnectedForDevice = false;
         boolean allProfilesEmpty = true;
         List<BluetoothDevice> a2dpConnDevList = null;
         List<BluetoothDevice> hsConnDevList = null;
@@ -357,106 +422,138 @@
 
         if (hsService != null) {
             hsConnDevList = hsService.getConnectedDevices();
-            allProfilesEmpty = allProfilesEmpty && hsConnDevList.isEmpty();
+            allProfilesEmpty &= hsConnDevList.isEmpty();
+            atLeastOneProfileConnectedForDevice |= hsConnDevList.contains(device);
         }
         if (a2dpService != null) {
             a2dpConnDevList = a2dpService.getConnectedDevices();
-            allProfilesEmpty = allProfilesEmpty && a2dpConnDevList.isEmpty();
+            allProfilesEmpty &= a2dpConnDevList.isEmpty();
+            atLeastOneProfileConnectedForDevice |= a2dpConnDevList.contains(device);
         }
         if (panService != null) {
             panConnDevList = panService.getConnectedDevices();
-            allProfilesEmpty = allProfilesEmpty && panConnDevList.isEmpty();
+            allProfilesEmpty &= panConnDevList.isEmpty();
+            atLeastOneProfileConnectedForDevice |= panConnDevList.contains(device);
         }
 
-        if (allProfilesEmpty) {
-            // considered as fully disconnected, don't bother connecting others.
+        if (!atLeastOneProfileConnectedForDevice) {
+            // Consider this device as fully disconnected, don't bother connecting others
             debugLog("processConnectOtherProfiles, all profiles disconnected for " + device);
-            // reset retry status so that in the next round we can start retrying connections again
-            resetStates();
+            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 (hsConnDevList.isEmpty() && !mHeadsetRetrySet.contains(device)
-                    && (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)
-                    && (hsService.getConnectionState(device)
-                               == BluetoothProfile.STATE_DISCONNECTED)) {
+            if (!mHeadsetRetrySet.contains(device) && (hsService.getPriority(device)
+                    >= BluetoothProfile.PRIORITY_ON) && (hsService.getConnectionState(device)
+                    == BluetoothProfile.STATE_DISCONNECTED)) {
                 debugLog("Retrying connection to Headset with device " + device);
                 mHeadsetRetrySet.add(device);
                 hsService.connect(device);
             }
         }
         if (a2dpService != null) {
-            if (a2dpConnDevList.isEmpty() && !mA2dpRetrySet.contains(device)
-                    && (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)
-                    && (a2dpService.getConnectionState(device)
-                               == BluetoothProfile.STATE_DISCONNECTED)) {
+            if (!mA2dpRetrySet.contains(device) && (a2dpService.getPriority(device)
+                    >= BluetoothProfile.PRIORITY_ON) && (a2dpService.getConnectionState(device)
+                    == BluetoothProfile.STATE_DISCONNECTED)) {
                 debugLog("Retrying connection to A2DP with device " + device);
                 mA2dpRetrySet.add(device);
                 a2dpService.connect(device);
             }
         }
         if (panService != null) {
-            if (panConnDevList.isEmpty()
-                    && (panService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)
-                    && (panService.getConnectionState(device)
-                               == BluetoothProfile.STATE_DISCONNECTED)) {
+            // TODO: the panConnDevList.isEmpty() check below should be removed once
+            // Multi-PAN is supported.
+            if (panConnDevList.isEmpty() && (panService.getPriority(device)
+                    >= BluetoothProfile.PRIORITY_ON) && (panService.getConnectionState(device)
+                    == BluetoothProfile.STATE_DISCONNECTED)) {
                 debugLog("Retrying connection to PAN with device " + device);
                 panService.connect(device);
             }
         }
     }
 
-    private void setProfileAutoConnectionPriority(BluetoothDevice device, int profileId) {
-        switch (profileId) {
-            case BluetoothProfile.HEADSET:
-                HeadsetService hsService = mFactory.getHeadsetService();
-                if ((hsService != null)
-                        && (BluetoothProfile.PRIORITY_AUTO_CONNECT
-                                   != hsService.getPriority(device))) {
-                    List<BluetoothDevice> deviceList = hsService.getConnectedDevices();
-                    adjustOtherHeadsetPriorities(hsService, deviceList);
-                    hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
-                }
-                break;
-
-            case BluetoothProfile.A2DP:
-                A2dpService a2dpService = mFactory.getA2dpService();
-                if ((a2dpService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT
-                                                     != a2dpService.getPriority(device))) {
-                    adjustOtherSinkPriorities(a2dpService, device);
-                    a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
-                }
-                break;
-
-            default:
-                Log.w(TAG, "Tried to set AutoConnect priority on invalid profile " + profileId);
-                break;
+    /**
+     * Set a device's headset profile priority to PRIORITY_AUTO_CONNECT if device support that
+     * profile
+     *
+     * @param device device whose headset profile priority should be PRIORITY_AUTO_CONNECT
+     */
+    private void setAutoConnectForHeadset(BluetoothDevice device) {
+        HeadsetService hsService = mFactory.getHeadsetService();
+        if (hsService == null) {
+            warnLog("setAutoConnectForHeadset: HEADSET service is null");
+            return;
+        }
+        if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
+            debugLog("setAutoConnectForHeadset: device " + device + " PRIORITY_AUTO_CONNECT");
+            hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
         }
     }
 
-    private void adjustOtherHeadsetPriorities(
-            HeadsetService hsService, List<BluetoothDevice> connectedDeviceList) {
-        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
-            if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
-                    && !connectedDeviceList.contains(device)) {
-                hsService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            }
+    /**
+     * Set a device's A2DP profile priority to PRIORITY_AUTO_CONNECT if device support that profile
+     *
+     * @param device device whose headset profile priority should be PRIORITY_AUTO_CONNECT
+     */
+    private void setAutoConnectForA2dpSink(BluetoothDevice device) {
+        A2dpService a2dpService = mFactory.getA2dpService();
+        if (a2dpService == null) {
+            warnLog("setAutoConnectForA2dpSink: A2DP service is null");
+            return;
+        }
+        if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
+            debugLog("setAutoConnectForA2dpSink: device " + device + " PRIORITY_AUTO_CONNECT");
+            a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
         }
     }
 
-    private void adjustOtherSinkPriorities(
-            A2dpService a2dpService, BluetoothDevice connectedDevice) {
-        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
-            if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT
-                    && !device.equals(connectedDevice)) {
-                a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            }
+    /**
+     * Remove PRIORITY_AUTO_CONNECT from all headsets and set headset that used to have
+     * PRIORITY_AUTO_CONNECT to PRIORITY_ON
+     *
+     * @param device device whose PRIORITY_AUTO_CONNECT priority should be removed
+     */
+    private void removeAutoConnectFromHeadset(BluetoothDevice device) {
+        HeadsetService hsService = mFactory.getHeadsetService();
+        if (hsService == null) {
+            warnLog("removeAutoConnectFromHeadset: HEADSET service is null");
+            return;
+        }
+        if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+            debugLog("removeAutoConnectFromHeadset: device " + device + " PRIORITY_ON");
+            hsService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        }
+    }
+
+    /**
+     * Remove PRIORITY_AUTO_CONNECT from all A2DP sinks and set A2DP sink that used to have
+     * PRIORITY_AUTO_CONNECT to PRIORITY_ON
+     *
+     * @param device device whose PRIORITY_AUTO_CONNECT priority should be removed
+     */
+    private void removeAutoConnectFromA2dpSink(BluetoothDevice device) {
+        A2dpService a2dpService = mFactory.getA2dpService();
+        if (a2dpService == null) {
+            warnLog("removeAutoConnectFromA2dpSink: A2DP service is null");
+            return;
+        }
+        if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+            debugLog("removeAutoConnectFromA2dpSink: device " + device + " PRIORITY_ON");
+            a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
         }
     }
 
     private static void debugLog(String msg) {
-        if (DBG) Log.d(TAG, msg);
+        if (DBG) {
+            Log.i(TAG, msg);
+        }
     }
 
     private static void warnLog(String msg) {
diff --git a/src/com/android/bluetooth/btservice/ProfileObserver.java b/src/com/android/bluetooth/btservice/ProfileObserver.java
index cccd96a..d5ddff4 100644
--- a/src/com/android/bluetooth/btservice/ProfileObserver.java
+++ b/src/com/android/bluetooth/btservice/ProfileObserver.java
@@ -26,9 +26,10 @@
     }
 
     public void start() {
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.BLUETOOTH_DISABLED_PROFILES), false,
-                this);
+        mContext.getContentResolver()
+                .registerContentObserver(
+                        Settings.Global.getUriFor(Settings.Global.BLUETOOTH_DISABLED_PROFILES),
+                        false, this);
     }
 
     private void onBluetoothOff() {
@@ -53,7 +54,7 @@
     private static class AdapterStateObserver extends BroadcastReceiver {
         private ProfileObserver mProfileObserver;
 
-        public AdapterStateObserver(ProfileObserver observer) {
+        AdapterStateObserver(ProfileObserver observer) {
             mProfileObserver = observer;
         }
 
diff --git a/src/com/android/bluetooth/btservice/ProfileService.java b/src/com/android/bluetooth/btservice/ProfileService.java
index cf53873..9a7186c 100644
--- a/src/com/android/bluetooth/btservice/ProfileService.java
+++ b/src/com/android/bluetooth/btservice/ProfileService.java
@@ -16,141 +16,149 @@
 
 package com.android.bluetooth.btservice;
 
-import java.util.HashMap;
-
-import com.android.bluetooth.Utils;
-
+import android.app.ActivityManager;
 import android.app.Service;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.Utils;
+
+/**
+ * Base class for a background service that runs a Bluetooth profile
+ */
 public abstract class ProfileService extends Service {
     private static final boolean DBG = false;
-    private static final String TAG = "BluetoothProfileService";
 
-    //For Debugging only
-    private static HashMap<String, Integer> sReferenceCount = new HashMap<String,Integer>();
-
-    public static final String BLUETOOTH_ADMIN_PERM =
-            android.Manifest.permission.BLUETOOTH_ADMIN;
+    public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     public static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
     public static final String BLUETOOTH_PRIVILEGED =
-        android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED;
 
-    public static interface IProfileServiceBinder extends IBinder {
-        public boolean cleanup();
+    public interface IProfileServiceBinder extends IBinder {
+        /**
+         * Called in {@link #onDestroy()}
+         */
+        void cleanup();
     }
+
     //Profile services will not be automatically restarted.
     //They must be explicitly restarted by AdapterService
-    private static final int PROFILE_SERVICE_MODE=Service.START_NOT_STICKY;
-    protected String mName;
-    protected BluetoothAdapter mAdapter;
-    protected IProfileServiceBinder mBinder;
-    protected boolean mStartError=false;
-    private boolean mCleaningUp = false;
+    private static final int PROFILE_SERVICE_MODE = Service.START_NOT_STICKY;
+    private BluetoothAdapter mAdapter;
+    private IProfileServiceBinder mBinder;
+    private final String mName;
+    private AdapterService mAdapterService;
+    private BroadcastReceiver mUserSwitchedReceiver;
+    private boolean mProfileStarted = false;
 
-    protected String getName() {
+    public String getName() {
         return getClass().getSimpleName();
     }
 
     protected boolean isAvailable() {
-        return !mStartError && !mCleaningUp;
+        return mProfileStarted;
     }
 
+    /**
+     * Called in {@link #onCreate()} to init binder interface for this profile service
+     *
+     * @return initialized binder interface for this profile service
+     */
     protected abstract IProfileServiceBinder initBinder();
 
-    protected abstract boolean start();
-    protected abstract boolean stop();
+    /**
+     * Called in {@link #onCreate()} to init basic stuff in this service
+     */
     protected void create() {}
-    protected boolean cleanup() {
-        return true;
-    }
+
+    /**
+     * Called in {@link #onStartCommand(Intent, int, int)} when the service is started by intent
+     *
+     * @return True in successful condition, False otherwise
+     */
+    protected abstract boolean start();
+
+    /**
+     * Called in {@link #onStartCommand(Intent, int, int)} when the service is stopped by intent
+     *
+     * @return True in successful condition, False otherwise
+     */
+    protected abstract boolean stop();
+
+    /**
+     * Called in {@link #onDestroy()} when this object is completely discarded
+     */
+    protected void cleanup() {}
+
+    /**
+     * @param userId is equivalent to the result of ActivityManager.getCurrentUser()
+     */
+    protected void setCurrentUser(int userId) {}
+
+    /**
+     * @param userId is equivalent to the result of ActivityManager.getCurrentUser()
+     */
+    protected void setUserUnlocked(int userId) {}
 
     protected ProfileService() {
         mName = getName();
-        if (DBG) {
-            synchronized (sReferenceCount) {
-                Integer refCount = sReferenceCount.get(mName);
-                if (refCount==null) {
-                    refCount = 1;
-                } else {
-                    refCount = refCount+1;
-                }
-                sReferenceCount.put(mName, refCount);
-                if (DBG) log("REFCOUNT: CREATED. INSTANCE_COUNT=" +refCount);
-            }
-        }
-    }
-
-    protected void finalize() {
-        if (DBG) {
-            synchronized (sReferenceCount) {
-                Integer refCount = sReferenceCount.get(mName);
-                if (refCount!=null) {
-                    refCount = refCount-1;
-                } else {
-                    refCount = 0;
-                }
-                sReferenceCount.put(mName, refCount);
-                log("REFCOUNT: FINALIZED. INSTANCE_COUNT=" +refCount);
-            }
-        }
     }
 
     @Override
     public void onCreate() {
-        if (DBG) log("onCreate");
+        if (DBG) {
+            Log.d(mName, "onCreate");
+        }
         super.onCreate();
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         mBinder = initBinder();
         create();
     }
 
+    @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        if (DBG) log("onStartCommand()");
-        AdapterService adapterService = AdapterService.getAdapterService();
-        if (adapterService != null) {
-            adapterService.addProfile(this);
-        } else {
-            Log.w(TAG, "Could not add this profile because AdapterService is null.");
+        if (DBG) {
+            Log.d(mName, "onStartCommand()");
         }
 
-        if (mStartError || mAdapter == null) {
-            Log.w(mName, "Stopping profile service: device does not have BT");
-            doStop(intent);
-            return PROFILE_SERVICE_MODE;
-        }
-
-        if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)!=PackageManager.PERMISSION_GRANTED) {
+        if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)
+                != PackageManager.PERMISSION_GRANTED) {
             Log.e(mName, "Permission denied!");
             return PROFILE_SERVICE_MODE;
         }
 
         if (intent == null) {
-            Log.d(mName, "Restarting profile service...");
+            Log.d(mName, "onStartCommand ignoring null intent.");
             return PROFILE_SERVICE_MODE;
-        } else {
-            String action = intent.getStringExtra(AdapterService.EXTRA_ACTION);
-            if (AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
-                int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
-                if(state==BluetoothAdapter.STATE_OFF) {
-                    Log.d(mName, "Received stop request...Stopping profile...");
-                    doStop(intent);
-                } else if (state == BluetoothAdapter.STATE_ON) {
-                    Log.d(mName, "Received start request. Starting profile...");
-                    doStart(intent);
-                }
+        }
+
+        String action = intent.getStringExtra(AdapterService.EXTRA_ACTION);
+        if (AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+            if (state == BluetoothAdapter.STATE_OFF) {
+                doStop();
+            } else if (state == BluetoothAdapter.STATE_ON) {
+                doStart();
             }
         }
         return PROFILE_SERVICE_MODE;
     }
 
+    @Override
     public IBinder onBind(Intent intent) {
-        if (DBG) log("onBind");
+        if (DBG) {
+            Log.d(mName, "onBind");
+        }
         if (mAdapter != null && mBinder == null) {
             // initBinder returned null, you can't bind
             throw new UnsupportedOperationException("Cannot bind to " + mName);
@@ -158,21 +166,40 @@
         return mBinder;
     }
 
+    @Override
     public boolean onUnbind(Intent intent) {
-        if (DBG) log("onUnbind");
+        if (DBG) {
+            Log.d(mName, "onUnbind");
+        }
         return super.onUnbind(intent);
     }
 
-    // for dumpsys support
+    /**
+     * Support dumping profile-specific information for dumpsys
+     *
+     * @param sb StringBuilder from the profile.
+     */
     public void dump(StringBuilder sb) {
-        sb.append("\nProfile: " + mName + "\n");
+        sb.append("\nProfile: ");
+        sb.append(mName);
+        sb.append("\n");
     }
 
-    public void dumpProto(BluetoothProto.BluetoothLog proto) {
+    /**
+     * Support dumping scan events from GattService
+     *
+     * @param builder metrics proto builder
+     */
+    public void dumpProto(BluetoothMetricsProto.BluetoothLog.Builder builder) {
         // Do nothing
     }
 
-    // with indenting for subclasses
+    /**
+     * Append an indented String for adding dumpsys support to subclasses.
+     *
+     * @param sb StringBuilder from the profile.
+     * @param s String to indent and append.
+     */
     public static void println(StringBuilder sb, String s) {
         sb.append("  ");
         sb.append(s);
@@ -181,66 +208,91 @@
 
     @Override
     public void onDestroy() {
-        if (DBG) log("Destroying service.");
-        AdapterService adapterService = AdapterService.getAdapterService();
-        if (adapterService != null) adapterService.removeProfile(this);
-
-        if (mCleaningUp) {
-            if (DBG) log("Cleanup already started... Skipping cleanup()...");
-        } else {
-            if (DBG) log("cleanup()");
-            mCleaningUp = true;
-            cleanup();
-            if (mBinder != null) {
-                mBinder.cleanup();
-                mBinder= null;
-            }
+        cleanup();
+        if (mBinder != null) {
+            mBinder.cleanup();
+            mBinder = null;
         }
-        super.onDestroy();
         mAdapter = null;
+        super.onDestroy();
     }
 
-    private void doStart(Intent intent) {
-        //Start service
+    private void doStart() {
         if (mAdapter == null) {
-            Log.e(mName, "Error starting profile. BluetoothAdapter is null");
-        } else {
-            if (DBG) log("start()");
-            mStartError = !start();
-            if (!mStartError) {
-                notifyProfileServiceStateChanged(BluetoothAdapter.STATE_ON);
-            } else {
-                Log.e(mName, "Error starting profile. BluetoothAdapter is null");
-            }
+            Log.w(mName, "Can't start profile service: device does not have BT");
+            return;
         }
+
+        mAdapterService = AdapterService.getAdapterService();
+        if (mAdapterService == null) {
+            Log.w(mName, "Could not add this profile because AdapterService is null.");
+            return;
+        }
+        mAdapterService.addProfile(this);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        mUserSwitchedReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                final int userId =
+                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                if (userId == UserHandle.USER_NULL) {
+                    Log.e(mName, "userChangeReceiver received an invalid EXTRA_USER_HANDLE");
+                    return;
+                }
+                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                    Log.d(mName, "User switched to userId " + userId);
+                    setCurrentUser(userId);
+                } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                    Log.d(mName, "Unlocked userId " + userId);
+                    setUserUnlocked(userId);
+                }
+            }
+        };
+
+        getApplicationContext().registerReceiver(mUserSwitchedReceiver, filter);
+        int currentUserId = ActivityManager.getCurrentUser();
+        setCurrentUser(currentUserId);
+        UserManager userManager = UserManager.get(getApplicationContext());
+        if (userManager.isUserUnlocked(currentUserId)) {
+            setUserUnlocked(currentUserId);
+        }
+        mProfileStarted = start();
+        if (!mProfileStarted) {
+            Log.e(mName, "Error starting profile. start() returned false.");
+            return;
+        }
+        mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_ON);
     }
 
-    private void doStop(Intent intent) {
-        if (stop()) {
-            if (DBG) log("stop()");
-            notifyProfileServiceStateChanged(BluetoothAdapter.STATE_OFF);
-            stopSelf();
-        } else {
+    private void doStop() {
+        if (!mProfileStarted) {
+            Log.w(mName, "doStop() called, but the profile is not running.");
+        }
+        mProfileStarted = false;
+        if (mAdapterService != null) {
+            mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_OFF);
+        }
+        if (!stop()) {
             Log.e(mName, "Unable to stop profile");
         }
-    }
-
-    protected void notifyProfileServiceStateChanged(int state) {
-        //Notify adapter service
-        AdapterService adapterService = AdapterService.getAdapterService();
-        if (adapterService != null) {
-            adapterService.onProfileServiceStateChanged(getClass().getName(), state);
+        if (mAdapterService != null) {
+            mAdapterService.removeProfile(this);
         }
+        if (mUserSwitchedReceiver != null) {
+            getApplicationContext().unregisterReceiver(mUserSwitchedReceiver);
+            mUserSwitchedReceiver = null;
+        }
+        stopSelf();
     }
 
     protected BluetoothDevice getDevice(byte[] address) {
-        if(mAdapter != null){
+        if (mAdapter != null) {
             return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
         }
         return null;
     }
-
-    protected void log(String msg) {
-        Log.d(mName, msg);
-    }
 }
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 01d9e4d..011177e 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -27,18 +27,22 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
+
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.hfp.HeadsetHalConstants;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.Queue;
+import java.util.Set;
 
 final class RemoteDevices {
     private static final boolean DBG = false;
@@ -47,9 +51,9 @@
     // Maximum number of device properties to remember
     private static final int MAX_DEVICE_QUEUE_SIZE = 200;
 
-    private static BluetoothAdapter mAdapter;
-    private static AdapterService mAdapterService;
-    private static ArrayList<BluetoothDevice> mSdpTracker;
+    private static BluetoothAdapter sAdapter;
+    private static AdapterService sAdapterService;
+    private static ArrayList<BluetoothDevice> sSdpTracker;
     private final Object mObject = new Object();
 
     private static final int UUID_INTENT_DELAY = 6000;
@@ -58,6 +62,30 @@
     private final HashMap<String, DeviceProperties> mDevices;
     private Queue<String> mDeviceQueue;
 
+    private final Handler mHandler;
+    private class RemoteDevicesHandler extends Handler {
+
+        /**
+         * Handler must be created from an explicit looper to avoid threading ambiguity
+         * @param looper The looper that this handler should be executed on
+         */
+        RemoteDevicesHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_UUID_INTENT:
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    if (device != null) {
+                        sendUuidIntent(device);
+                    }
+                    break;
+            }
+        }
+    }
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -79,12 +107,13 @@
         }
     };
 
-    RemoteDevices(AdapterService service) {
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        mAdapterService = service;
-        mSdpTracker = new ArrayList<BluetoothDevice>();
+    RemoteDevices(AdapterService service, Looper looper) {
+        sAdapter = BluetoothAdapter.getDefaultAdapter();
+        sAdapterService = service;
+        sSdpTracker = new ArrayList<BluetoothDevice>();
         mDevices = new HashMap<String, DeviceProperties>();
         mDeviceQueue = new LinkedList<String>();
+        mHandler = new RemoteDevicesHandler(looper);
     }
 
     /**
@@ -99,7 +128,7 @@
         filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "."
                 + BluetoothAssignedNumbers.APPLE);
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        mAdapterService.registerReceiver(mReceiver, filter);
+        sAdapterService.registerReceiver(mReceiver, filter);
     }
 
     /**
@@ -107,7 +136,7 @@
      */
     void cleanup() {
         // Unregister receiver first, mAdapterService is never null
-        mAdapterService.unregisterReceiver(mReceiver);
+        sAdapterService.unregisterReceiver(mReceiver);
         reset();
     }
 
@@ -116,14 +145,17 @@
      * RemoteDevices is still usable after reset
      */
     void reset() {
-        if (mSdpTracker !=null)
-            mSdpTracker.clear();
+        if (sSdpTracker != null) {
+            sSdpTracker.clear();
+        }
 
-        if (mDevices != null)
+        if (mDevices != null) {
             mDevices.clear();
+        }
 
-        if (mDeviceQueue != null)
+        if (mDeviceQueue != null) {
             mDeviceQueue.clear();
+        }
     }
 
     @Override
@@ -139,15 +171,17 @@
 
     BluetoothDevice getDevice(byte[] address) {
         DeviceProperties prop = mDevices.get(Utils.getAddressStringFromByte(address));
-        if (prop == null)
-           return null;
+        if (prop == null) {
+            return null;
+        }
         return prop.getDevice();
     }
 
+    @VisibleForTesting
     DeviceProperties addDeviceProperties(byte[] address) {
         synchronized (mDevices) {
             DeviceProperties prop = new DeviceProperties();
-            prop.mDevice = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
+            prop.mDevice = sAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
             prop.mAddress = address;
             String key = Utils.getAddressStringFromByte(address);
             DeviceProperties pv = mDevices.put(key, prop);
@@ -156,8 +190,10 @@
                 mDeviceQueue.offer(key);
                 if (mDeviceQueue.size() > MAX_DEVICE_QUEUE_SIZE) {
                     String deleteKey = mDeviceQueue.poll();
-                    for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
-                        if (device.getAddress().equals(deleteKey)) return prop;
+                    for (BluetoothDevice device : sAdapterService.getBondedDevices()) {
+                        if (device.getAddress().equals(deleteKey)) {
+                            return prop;
+                        }
                     }
                     debugLog("Removing device " + deleteKey + " from property map");
                     mDevices.remove(deleteKey);
@@ -172,13 +208,13 @@
         private byte[] mAddress;
         private int mBluetoothClass = BluetoothClass.Device.Major.UNCATEGORIZED;
         private short mRssi;
-        private ParcelUuid[] mUuids;
-        private int mDeviceType;
         private String mAlias;
-        private int mBondState;
         private BluetoothDevice mDevice;
-        private boolean isBondingInitiatedLocally;
+        private boolean mIsBondingInitiatedLocally;
         private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+        @VisibleForTesting int mBondState;
+        @VisibleForTesting int mDeviceType;
+        @VisibleForTesting ParcelUuid[] mUuids;
 
         DeviceProperties() {
             mBondState = BluetoothDevice.BOND_NONE;
@@ -262,12 +298,12 @@
         void setAlias(BluetoothDevice device, String mAlias) {
             synchronized (mObject) {
                 this.mAlias = mAlias;
-                mAdapterService.setDevicePropertyNative(mAddress,
-                    AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME, mAlias.getBytes());
+                sAdapterService.setDevicePropertyNative(mAddress,
+                        AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME, mAlias.getBytes());
                 Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED);
                 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
                 intent.putExtra(BluetoothDevice.EXTRA_NAME, mAlias);
-                mAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
+                sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
             }
         }
 
@@ -277,8 +313,7 @@
         void setBondState(int mBondState) {
             synchronized (mObject) {
                 this.mBondState = mBondState;
-                if (mBondState == BluetoothDevice.BOND_NONE)
-                {
+                if (mBondState == BluetoothDevice.BOND_NONE) {
                     /* Clearing the Uuids local copy when the device is unpaired. If not cleared,
                     cachedBluetoothDevice issued a connect using the local cached copy of uuids,
                     without waiting for the ACTION_UUID intent.
@@ -302,7 +337,7 @@
          */
         void setBondingInitiatedLocally(boolean isBondingInitiatedLocally) {
             synchronized (mObject) {
-                this.isBondingInitiatedLocally = isBondingInitiatedLocally;
+                this.mIsBondingInitiatedLocally = isBondingInitiatedLocally;
             }
         }
 
@@ -311,7 +346,7 @@
          */
         boolean isBondingInitiatedLocally() {
             synchronized (mObject) {
-                return isBondingInitiatedLocally;
+                return mIsBondingInitiatedLocally;
             }
         }
 
@@ -336,10 +371,10 @@
         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothDevice.EXTRA_UUID, prop == null ? null : prop.mUuids);
-        mAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
+        sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
 
         //Remove the outstanding UUID request
-        mSdpTracker.remove(device);
+        sSdpTracker.remove(device);
     }
 
     /**
@@ -380,8 +415,8 @@
         synchronized (mObject) {
             int currentBatteryLevel = deviceProperties.getBatteryLevel();
             if (batteryLevel == currentBatteryLevel) {
-                debugLog("Same battery level for device " + device + " received "
-                        + String.valueOf(batteryLevel) + "%");
+                debugLog("Same battery level for device " + device + " received " + String.valueOf(
+                        batteryLevel) + "%");
                 return;
             }
             deviceProperties.setBatteryLevel(batteryLevel);
@@ -420,7 +455,23 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, batteryLevel);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
+        sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
+    }
+
+    private static boolean areUuidsEqual(ParcelUuid[] uuids1, ParcelUuid[] uuids2) {
+        final int length1 = uuids1 == null ? 0 : uuids1.length;
+        final int length2 = uuids2 == null ? 0 : uuids2.length;
+        if (length1 != length2) {
+            return false;
+        }
+        Set<ParcelUuid> set = new HashSet<>();
+        for (int i = 0; i < length1; ++i) {
+            set.add(uuids1[i]);
+        }
+        for (int i = 0; i < length2; ++i) {
+            set.remove(uuids2[i]);
+        }
+        return set.isEmpty();
     }
 
     void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) {
@@ -446,45 +497,56 @@
             type = types[j];
             val = values[j];
             if (val.length > 0) {
-                synchronized(mObject) {
+                synchronized (mObject) {
                     debugLog("Property type: " + type);
                     switch (type) {
                         case AbstractionLayer.BT_PROPERTY_BDNAME:
-                            device.mName = new String(val);
+                            final String newName = new String(val);
+                            if (newName.equals(device.mName)) {
+                                Log.w(TAG, "Skip name update for " + bdDevice);
+                                break;
+                            }
+                            device.mName = newName;
                             intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);
                             intent.putExtra(BluetoothDevice.EXTRA_NAME, device.mName);
                             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                            mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);
+                            sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
                             debugLog("Remote Device name is: " + device.mName);
                             break;
                         case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME:
-                            if (device.mAlias != null) {
-                                System.arraycopy(val, 0, device.mAlias, 0, val.length);
-                            }
-                            else {
-                                device.mAlias = new String(val);
-                            }
+                            device.mAlias = new String(val);
+                            debugLog("Remote device alias is: " + device.mAlias);
                             break;
                         case AbstractionLayer.BT_PROPERTY_BDADDR:
                             device.mAddress = val;
                             debugLog("Remote Address is:" + Utils.getAddressStringFromByte(val));
                             break;
                         case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE:
-                            device.mBluetoothClass =  Utils.byteArrayToInt(val);
+                            final int newClass = Utils.byteArrayToInt(val);
+                            if (newClass == device.mBluetoothClass) {
+                                Log.w(TAG, "Skip class update for " + bdDevice);
+                                break;
+                            }
+                            device.mBluetoothClass = Utils.byteArrayToInt(val);
                             intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);
                             intent.putExtra(BluetoothDevice.EXTRA_CLASS,
                                     new BluetoothClass(device.mBluetoothClass));
                             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                            mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);
+                            sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
                             debugLog("Remote class is:" + device.mBluetoothClass);
                             break;
                         case AbstractionLayer.BT_PROPERTY_UUIDS:
-                            int numUuids = val.length/AbstractionLayer.BT_UUID_SIZE;
-                            device.mUuids = Utils.byteArrayToUuid(val);
-                            if (mAdapterService.getState() == BluetoothAdapter.STATE_ON) {
-                                mAdapterService.deviceUuidUpdated(bdDevice);
+                            int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE;
+                            final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val);
+                            if (areUuidsEqual(newUuids, device.mUuids)) {
+                                Log.w(TAG, "Skip uuids update for " + bdDevice.getAddress());
+                                break;
+                            }
+                            device.mUuids = newUuids;
+                            if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {
+                                sAdapterService.deviceUuidUpdated(bdDevice);
                                 sendUuidIntent(bdDevice);
                             }
                             break;
@@ -521,9 +583,9 @@
         intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
         intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);
 
-        mAdapterService.sendBroadcastMultiplePermissions(intent,
-                new String[] {AdapterService.BLUETOOTH_PERM,
-                        android.Manifest.permission.ACCESS_COARSE_LOCATION});
+        sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{
+                AdapterService.BLUETOOTH_PERM, android.Manifest.permission.ACCESS_COARSE_LOCATION
+        });
     }
 
     void aclStateChangeCallback(int status, byte[] address, int newState) {
@@ -534,36 +596,40 @@
                     + Utils.getAddressStringFromByte(address) + ", newState=" + newState);
             return;
         }
-        int state = mAdapterService.getState();
+        int state = sAdapterService.getState();
 
         Intent intent = null;
         if (newState == AbstractionLayer.BT_ACL_STATE_CONNECTED) {
             if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_ON) {
                 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
-            } else if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON) {
+            } else if (state == BluetoothAdapter.STATE_BLE_ON
+                    || state == BluetoothAdapter.STATE_BLE_TURNING_ON) {
                 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_CONNECTED);
             }
-            debugLog("aclStateChangeCallback: Adapter State: "
-                    + BluetoothAdapter.nameForState(state) + " Connected: " + device);
+            debugLog(
+                    "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state)
+                            + " Connected: " + device);
         } else {
             if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
                 // Send PAIRING_CANCEL intent to dismiss any dialog requesting bonding.
                 intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
                 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-                intent.setPackage(mAdapterService.getString(R.string.pairing_ui_package));
-                mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);
+                intent.setPackage(sAdapterService.getString(R.string.pairing_ui_package));
+                sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
             }
             if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_OFF) {
                 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
-            } else if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
+            } else if (state == BluetoothAdapter.STATE_BLE_ON
+                    || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
                 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_DISCONNECTED);
             }
             // Reset battery level on complete disconnection
-            if (mAdapterService.getConnectionState(device) == 0) {
+            if (sAdapterService.getConnectionState(device) == 0) {
                 resetBatteryLevel(device);
             }
-            debugLog("aclStateChangeCallback: Adapter State: "
-                    + BluetoothAdapter.nameForState(state) + " Disconnected: " + device);
+            debugLog(
+                    "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state)
+                            + " Disconnected: " + device);
         }
 
         if (intent != null) {
@@ -571,7 +637,7 @@
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                     | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);
+            sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
         } else {
             Log.e(TAG, "aclStateChangeCallback intent is null. deviceBondState: "
                     + device.getBondState());
@@ -580,14 +646,16 @@
 
 
     void fetchUuids(BluetoothDevice device) {
-        if (mSdpTracker.contains(device)) return;
-        mSdpTracker.add(device);
+        if (sSdpTracker.contains(device)) {
+            return;
+        }
+        sSdpTracker.add(device);
 
         Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
         message.obj = device;
         mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY);
 
-        mAdapterService.getRemoteServicesNative(Utils.getBytesFromAddress(device.getAddress()));
+        sAdapterService.getRemoteServicesNative(Utils.getBytesFromAddress(device.getAddress()));
     }
 
     void updateUuids(BluetoothDevice device) {
@@ -645,15 +713,16 @@
             Log.e(TAG, "onVendorSpecificHeadsetEvent() command is null");
             return;
         }
-        int cmdType = intent.getIntExtra(
-                BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, -1);
+        int cmdType =
+                intent.getIntExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE,
+                        -1);
         // Only process set command
         if (cmdType != BluetoothHeadset.AT_CMD_TYPE_SET) {
             debugLog("onVendorSpecificHeadsetEvent() only SET command is processed");
             return;
         }
-        Object[] args = (Object[]) intent.getExtras().get(
-                BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS);
+        Object[] args = (Object[]) intent.getExtras()
+                .get(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS);
         if (args == null) {
             Log.e(TAG, "onVendorSpecificHeadsetEvent() arguments are null");
             return;
@@ -669,8 +738,8 @@
         }
         if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
             updateBatteryLevel(device, batteryPercent);
-            infoLog("Updated device " + device + " battery level to "
-                    + String.valueOf(batteryPercent) + "%");
+            infoLog("Updated device " + device + " battery level to " + String.valueOf(
+                    batteryPercent) + "%");
         }
     }
 
@@ -723,7 +792,7 @@
             break;
         }
         return (indicatorValue < 0 || indicatorValue > 9) ? BluetoothDevice.BATTERY_LEVEL_UNKNOWN
-                                                          : (indicatorValue + 1) * 10;
+                : (indicatorValue + 1) * 10;
     }
 
     /**
@@ -747,13 +816,13 @@
         }
         String eventName = (String) eventNameObj;
         if (!eventName.equals(
-                    BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL)) {
+                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL)) {
             infoLog("getBatteryLevelFromXEventVsc() skip none BATTERY event: " + eventName);
             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
         }
         if (args.length != 5) {
             Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong battery level event length: "
-                            + String.valueOf(args.length));
+                    + String.valueOf(args.length));
             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
         }
         if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)) {
@@ -764,37 +833,27 @@
         int numberOfLevels = (Integer) args[2];
         if (batteryLevel < 0 || numberOfLevels < 0 || batteryLevel > numberOfLevels) {
             Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong event value, batteryLevel="
-                            + String.valueOf(batteryLevel) + ", numberOfLevels="
-                            + String.valueOf(numberOfLevels));
+                    + String.valueOf(batteryLevel) + ", numberOfLevels=" + String.valueOf(
+                    numberOfLevels));
             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
         }
         return batteryLevel * 100 / numberOfLevels;
     }
 
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-            case MESSAGE_UUID_INTENT:
-                BluetoothDevice device = (BluetoothDevice)msg.obj;
-                if (device != null) {
-                    sendUuidIntent(device);
-                }
-                break;
-            }
-        }
-    };
-
     private static void errorLog(String msg) {
         Log.e(TAG, msg);
     }
 
     private static void debugLog(String msg) {
-        if (DBG) Log.d(TAG, msg);
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
     }
 
     private static void infoLog(String msg) {
-        if (DBG) Log.i(TAG, msg);
+        if (DBG) {
+            Log.i(TAG, msg);
+        }
     }
 
     private static void warnLog(String msg) {
diff --git a/src/com/android/bluetooth/btservice/ServiceFactory.java b/src/com/android/bluetooth/btservice/ServiceFactory.java
index c4b8ab4..2bd7ab5 100644
--- a/src/com/android/bluetooth/btservice/ServiceFactory.java
+++ b/src/com/android/bluetooth/btservice/ServiceFactory.java
@@ -17,10 +17,11 @@
 package com.android.bluetooth.btservice;
 
 import com.android.bluetooth.a2dp.A2dpService;
-import com.android.bluetooth.hid.HidService;
+import com.android.bluetooth.hearingaid.HearingAidService;
 import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hid.HidDeviceService;
+import com.android.bluetooth.hid.HidHostService;
 import com.android.bluetooth.pan.PanService;
-import com.android.internal.R;
 
 // Factory class to create instances of static services. Useful in mocking the service objects.
 public class ServiceFactory {
@@ -32,11 +33,19 @@
         return HeadsetService.getHeadsetService();
     }
 
-    public HidService getHidService() {
-        return HidService.getHidService();
+    public HidHostService getHidHostService() {
+        return HidHostService.getHidHostService();
+    }
+
+    public HidDeviceService getHidDeviceService() {
+        return HidDeviceService.getHidDeviceService();
     }
 
     public PanService getPanService() {
         return PanService.getPanService();
     }
+
+    public HearingAidService getHearingAidService() {
+        return HearingAidService.getHearingAidService();
+    }
 }
diff --git a/src/com/android/bluetooth/btservice/bluetooth.proto b/src/com/android/bluetooth/btservice/bluetooth.proto
deleted file mode 100644
index c616c10..0000000
--- a/src/com/android/bluetooth/btservice/bluetooth.proto
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright 2014 Google Inc. All Rights Reserved.
-// Author: pkanwar@google.com (Pankaj Kanwar)
-// Protos for uploading bluetooth metrics.
-
-syntax = "proto2";
-
-package com.android.bluetooth.btservice;
-
-option java_package = "com.android.bluetooth.btservice";
-option java_outer_classname = "BluetoothProto";
-//option (datapol.file_vetting_status) = "latest";
-
-// import "storage/datapol/annotations/proto/semantic_annotations.proto";
-
-message BluetoothLog {
-
-  // Session information that gets logged for every BT connection.
-  repeated BluetoothSession session = 1;
-
-  // Session information that gets logged for every Pair event.
-  repeated PairEvent pair_event = 2;
-
-  // Information for Wake locks.
-  repeated WakeEvent wake_event = 3;
-
-  // Scan event information.
-  repeated ScanEvent scan_event = 4;
-
-  // Number of bonded devices.
-  optional int32 num_bonded_devices = 5;
-
-  // Number of BluetoothSession including discarded ones beyond capacity
-  optional int64 num_bluetooth_session = 6;
-
-  // Number of PairEvent including discarded ones beyond capacity
-  optional int64 num_pair_event = 7;
-
-  // Number of WakeEvent including discarded ones beyond capacity
-  optional int64 num_wake_event = 8;
-
-  // Number of ScanEvent including discarded ones beyond capacity
-  optional int64 num_scan_event = 9;
-}
-
-// The information about the device.
-message DeviceInfo {
-
-  // Device type.
-  enum DeviceType {
-
-     // Type is unknown.
-     DEVICE_TYPE_UNKNOWN = 0;
-
-     DEVICE_TYPE_BREDR = 1;
-
-     DEVICE_TYPE_LE = 2;
-
-     DEVICE_TYPE_DUMO = 3;
-  }
-
-  // Device class
-  // https://cs.corp.google.com/#android/system/bt/stack/include/btm_api.h&q=major_computer.
-  optional int32 device_class = 1;
-
-  // Device type.
-  optional DeviceType device_type = 2;
-}
-
-// Information that gets logged for every Bluetooth connection.
-message BluetoothSession {
-
-  // Type of technology used in the connection.
-  enum ConnectionTechnologyType {
-
-     CONNECTION_TECHNOLOGY_TYPE_UNKNOWN = 0;
-
-     CONNECTION_TECHNOLOGY_TYPE_LE = 1;
-
-     CONNECTION_TECHNOLOGY_TYPE_BREDR = 2;
-  }
-
-  // Duration of the session.
-  optional int64 session_duration_sec = 2;
-
-  // Technology type.
-  optional ConnectionTechnologyType connection_technology_type = 3;
-
-  // Reason for disconnecting.
-  optional string disconnect_reason = 4;
-
-  // The information about the device which it is connected to.
-  optional DeviceInfo device_connected_to = 5;
-
-  // The information about the RFComm session.
-  optional RFCommSession rfcomm_session = 6;
-
-  // The information about the A2DP audio session.
-  optional A2DPSession a2dp_session = 7;
-}
-
-message RFCommSession {
-
-  // bytes transmitted.
-  optional int32 rx_bytes = 1;
-
-  // bytes transmitted.
-  optional int32 tx_bytes = 2;
-}
-
-// Session information that gets logged for A2DP session.
-message A2DPSession {
-
-  // Media timer in milliseconds.
-  optional int32 media_timer_min_millis = 1;
-
-  // Media timer in milliseconds.
-  optional int32 media_timer_max_millis = 2;
-
-  // Media timer in milliseconds.
-  optional int32 media_timer_avg_millis = 3;
-
-  // Buffer overruns count.
-  optional int32 buffer_overruns_max_count = 4;
-
-  // Buffer overruns total.
-  optional int32 buffer_overruns_total = 5;
-
-  // Buffer underruns average.
-  optional float buffer_underruns_average = 6;
-
-  // Buffer underruns count.
-  optional int32 buffer_underruns_count = 7;
-
-  // Total audio time in this A2DP session
-  optional int64 audio_duration_millis = 8;
-}
-
-message PairEvent {
-
-  // The reason for disconnecting
-  // https://cs.corp.google.com/#android/system/bt/stack/include/hcidefs.h&q=failed_establish.
-  optional int32 disconnect_reason = 1;
-
-  // Pair event time
-  optional int64 event_time_millis = 2; // [(datapol.semantic_type) = ST_TIMESTAMP];
-
-  // The information about the device which it is paired to.
-  optional DeviceInfo device_paired_with = 3;
-}
-
-message WakeEvent {
-
-  // Information about the wake event type.
-  enum WakeEventType {
-
-     // Type is unknown.
-     UNKNOWN = 0;
-
-     // WakeLock was acquired.
-     ACQUIRED = 1;
-
-     // WakeLock was released.
-     RELEASED = 2;
-  }
-
-  // Information about the wake event type.
-  optional WakeEventType wake_event_type = 1;
-
-  // Initiator of the scan. Only the first three names will be stored.
-  // e.g. com.google.gms.
-  optional string requestor = 2;
-
-  // Name of the wakelock (e.g. bluedroid_timer).
-  optional string name = 3;
-
-  // Time of the event.
-  optional int64 event_time_millis = 4; // [(datapol.semantic_type) = ST_TIMESTAMP];
-}
-
-message ScanEvent {
-
-  // Scan type.
-  enum ScanTechnologyType {
-
-     // Scan Type is unknown.
-     SCAN_TYPE_UNKNOWN = 0;
-
-     SCAN_TECH_TYPE_LE = 1;
-
-     SCAN_TECH_TYPE_BREDR = 2;
-
-     SCAN_TECH_TYPE_BOTH = 3;
-  }
-
-  // Scan event type.
-  enum ScanEventType {
-
-     // Scan started.
-     SCAN_EVENT_START = 0;
-
-     // Scan stopped.
-     SCAN_EVENT_STOP = 1;
-  }
-
-  // Scan event type.
-  optional ScanEventType scan_event_type = 1;
-
-  // Initiator of the scan. Only the first three names will be stored.
-  // e.g. com.google.gms.
-  optional string initiator = 2;
-
-  // Technology used for scanning.
-  optional ScanTechnologyType scan_technology_type = 3;
-
-  // Number of results returned.
-  optional int32 number_results = 4;
-
-  // Time of the event.
-  optional int64 event_time_millis = 5; // [(datapol.semantic_type) = ST_TIMESTAMP];
-}
diff --git a/src/com/android/bluetooth/gatt/AdvertiseHelper.java b/src/com/android/bluetooth/gatt/AdvertiseHelper.java
index 9355631..1c1b4dc 100644
--- a/src/com/android/bluetooth/gatt/AdvertiseHelper.java
+++ b/src/com/android/bluetooth/gatt/AdvertiseHelper.java
@@ -20,150 +20,152 @@
 import android.bluetooth.le.AdvertiseData;
 import android.os.ParcelUuid;
 import android.util.Log;
-import com.android.bluetooth.Utils;
+
 import java.io.ByteArrayOutputStream;
 
 class AdvertiseHelper {
 
-  private static final String TAG = "AdvertiseHelper";
+    private static final String TAG = "AdvertiseHelper";
 
-  private static final int DEVICE_NAME_MAX = 26;
+    private static final int DEVICE_NAME_MAX = 26;
 
-  private static final int COMPLETE_LIST_16_BIT_SERVICE_UUIDS = 0X03;
-  private static final int COMPLETE_LIST_32_BIT_SERVICE_UUIDS = 0X05;
-  private static final int COMPLETE_LIST_128_BIT_SERVICE_UUIDS = 0X07;
-  private static final int SHORTENED_LOCAL_NAME = 0X08;
-  private static final int COMPLETE_LOCAL_NAME = 0X09;
-  private static final int TX_POWER_LEVEL = 0x0A;
-  private static final int SERVICE_DATA_16_BIT_UUID = 0X16;
-  private static final int SERVICE_DATA_32_BIT_UUID = 0X20;
-  private static final int SERVICE_DATA_128_BIT_UUID = 0X21;
-  private static final int MANUFACTURER_SPECIFIC_DATA = 0XFF;
+    private static final int COMPLETE_LIST_16_BIT_SERVICE_UUIDS = 0X03;
+    private static final int COMPLETE_LIST_32_BIT_SERVICE_UUIDS = 0X05;
+    private static final int COMPLETE_LIST_128_BIT_SERVICE_UUIDS = 0X07;
+    private static final int SHORTENED_LOCAL_NAME = 0X08;
+    private static final int COMPLETE_LOCAL_NAME = 0X09;
+    private static final int TX_POWER_LEVEL = 0x0A;
+    private static final int SERVICE_DATA_16_BIT_UUID = 0X16;
+    private static final int SERVICE_DATA_32_BIT_UUID = 0X20;
+    private static final int SERVICE_DATA_128_BIT_UUID = 0X21;
+    private static final int MANUFACTURER_SPECIFIC_DATA = 0XFF;
 
-  public static byte[] advertiseDataToBytes(AdvertiseData data, String name) {
+    public static byte[] advertiseDataToBytes(AdvertiseData data, String name) {
 
-    if (data == null) return new byte[0];
-
-    // Flags are added by lower layers of the stack, only if needed;
-    // no need to add them here.
-
-    ByteArrayOutputStream ret = new ByteArrayOutputStream();
-
-    if (data.getIncludeDeviceName()) {
-      try {
-        byte[] nameBytes = name.getBytes("UTF-8");
-
-        int nameLength = nameBytes.length;
-        byte type;
-
-        // TODO(jpawlowski) put a better limit on device name!
-        if (nameLength > DEVICE_NAME_MAX) {
-          nameLength = DEVICE_NAME_MAX;
-          type = SHORTENED_LOCAL_NAME;
-        } else {
-          type = COMPLETE_LOCAL_NAME;
+        if (data == null) {
+            return new byte[0];
         }
 
-        ret.write(nameLength + 1);
-        ret.write(type);
-        ret.write(nameBytes, 0, nameLength);
-      } catch (java.io.UnsupportedEncodingException e) {
-        Log.e(TAG, "Can't include name - encoding error!", e);
-      }
-    }
+        // Flags are added by lower layers of the stack, only if needed;
+        // no need to add them here.
 
-    for (int i = 0; i < data.getManufacturerSpecificData().size(); i++) {
-      int manufacturerId = data.getManufacturerSpecificData().keyAt(i);
+        ByteArrayOutputStream ret = new ByteArrayOutputStream();
 
-      byte[] manufacturerData = data.getManufacturerSpecificData().get(manufacturerId);
-      int dataLen = 2 + (manufacturerData == null ? 0 : manufacturerData.length);
-      byte[] concated = new byte[dataLen];
-      // First two bytes are manufacturer id in little-endian.
-      concated[0] = (byte) (manufacturerId & 0xFF);
-      concated[1] = (byte) ((manufacturerId >> 8) & 0xFF);
-      if (manufacturerData != null) {
-        System.arraycopy(manufacturerData, 0, concated, 2, manufacturerData.length);
-      }
+        if (data.getIncludeDeviceName()) {
+            try {
+                byte[] nameBytes = name.getBytes("UTF-8");
 
-      ret.write(concated.length + 1);
-      ret.write(MANUFACTURER_SPECIFIC_DATA);
-      ret.write(concated, 0, concated.length);
-    }
+                int nameLength = nameBytes.length;
+                byte type;
 
-    if (data.getIncludeTxPowerLevel()) {
-      ret.write(2 /* Length */);
-      ret.write(TX_POWER_LEVEL);
-      ret.write(0); // lower layers will fill this value.
-    }
+                // TODO(jpawlowski) put a better limit on device name!
+                if (nameLength > DEVICE_NAME_MAX) {
+                    nameLength = DEVICE_NAME_MAX;
+                    type = SHORTENED_LOCAL_NAME;
+                } else {
+                    type = COMPLETE_LOCAL_NAME;
+                }
 
-    if (data.getServiceUuids() != null) {
-      ByteArrayOutputStream serviceUuids16 = new ByteArrayOutputStream();
-      ByteArrayOutputStream serviceUuids32 = new ByteArrayOutputStream();
-      ByteArrayOutputStream serviceUuids128 = new ByteArrayOutputStream();
-
-      for (ParcelUuid parcelUuid : data.getServiceUuids()) {
-        byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid);
-
-        if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) {
-          serviceUuids16.write(uuid, 0, uuid.length);
-        } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) {
-          serviceUuids32.write(uuid, 0, uuid.length);
-        } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ {
-          serviceUuids128.write(uuid, 0, uuid.length);
-        }
-      }
-
-      if (serviceUuids16.size() != 0) {
-        ret.write(serviceUuids16.size() + 1);
-        ret.write(COMPLETE_LIST_16_BIT_SERVICE_UUIDS);
-        ret.write(serviceUuids16.toByteArray(), 0, serviceUuids16.size());
-      }
-
-      if (serviceUuids32.size() != 0) {
-        ret.write(serviceUuids32.size() + 1);
-        ret.write(COMPLETE_LIST_32_BIT_SERVICE_UUIDS);
-        ret.write(serviceUuids32.toByteArray(), 0, serviceUuids32.size());
-      }
-
-      if (serviceUuids128.size() != 0) {
-        ret.write(serviceUuids128.size() + 1);
-        ret.write(COMPLETE_LIST_128_BIT_SERVICE_UUIDS);
-        ret.write(serviceUuids128.toByteArray(), 0, serviceUuids128.size());
-      }
-    }
-
-    if (!data.getServiceData().isEmpty()) {
-      for (ParcelUuid parcelUuid : data.getServiceData().keySet()) {
-        byte[] serviceData = data.getServiceData().get(parcelUuid);
-
-        byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid);
-        int uuidLen = uuid.length;
-
-        int dataLen = uuidLen + (serviceData == null ? 0 : serviceData.length);
-        byte[] concated = new byte[dataLen];
-
-        System.arraycopy(uuid, 0, concated, 0, uuidLen);
-
-        if (serviceData != null) {
-          System.arraycopy(serviceData, 0, concated, uuidLen, serviceData.length);
+                ret.write(nameLength + 1);
+                ret.write(type);
+                ret.write(nameBytes, 0, nameLength);
+            } catch (java.io.UnsupportedEncodingException e) {
+                Log.e(TAG, "Can't include name - encoding error!", e);
+            }
         }
 
-        if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) {
-          ret.write(concated.length + 1);
-          ret.write(SERVICE_DATA_16_BIT_UUID);
-          ret.write(concated, 0, concated.length);
-        } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) {
-          ret.write(concated.length + 1);
-          ret.write(SERVICE_DATA_32_BIT_UUID);
-          ret.write(concated, 0, concated.length);
-        } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ {
-          ret.write(concated.length + 1);
-          ret.write(SERVICE_DATA_128_BIT_UUID);
-          ret.write(concated, 0, concated.length);
-        }
-      }
-    }
+        for (int i = 0; i < data.getManufacturerSpecificData().size(); i++) {
+            int manufacturerId = data.getManufacturerSpecificData().keyAt(i);
 
-    return ret.toByteArray();
-  }
+            byte[] manufacturerData = data.getManufacturerSpecificData().get(manufacturerId);
+            int dataLen = 2 + (manufacturerData == null ? 0 : manufacturerData.length);
+            byte[] concated = new byte[dataLen];
+            // First two bytes are manufacturer id in little-endian.
+            concated[0] = (byte) (manufacturerId & 0xFF);
+            concated[1] = (byte) ((manufacturerId >> 8) & 0xFF);
+            if (manufacturerData != null) {
+                System.arraycopy(manufacturerData, 0, concated, 2, manufacturerData.length);
+            }
+
+            ret.write(concated.length + 1);
+            ret.write(MANUFACTURER_SPECIFIC_DATA);
+            ret.write(concated, 0, concated.length);
+        }
+
+        if (data.getIncludeTxPowerLevel()) {
+            ret.write(2 /* Length */);
+            ret.write(TX_POWER_LEVEL);
+            ret.write(0); // lower layers will fill this value.
+        }
+
+        if (data.getServiceUuids() != null) {
+            ByteArrayOutputStream serviceUuids16 = new ByteArrayOutputStream();
+            ByteArrayOutputStream serviceUuids32 = new ByteArrayOutputStream();
+            ByteArrayOutputStream serviceUuids128 = new ByteArrayOutputStream();
+
+            for (ParcelUuid parcelUuid : data.getServiceUuids()) {
+                byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid);
+
+                if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) {
+                    serviceUuids16.write(uuid, 0, uuid.length);
+                } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) {
+                    serviceUuids32.write(uuid, 0, uuid.length);
+                } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ {
+                    serviceUuids128.write(uuid, 0, uuid.length);
+                }
+            }
+
+            if (serviceUuids16.size() != 0) {
+                ret.write(serviceUuids16.size() + 1);
+                ret.write(COMPLETE_LIST_16_BIT_SERVICE_UUIDS);
+                ret.write(serviceUuids16.toByteArray(), 0, serviceUuids16.size());
+            }
+
+            if (serviceUuids32.size() != 0) {
+                ret.write(serviceUuids32.size() + 1);
+                ret.write(COMPLETE_LIST_32_BIT_SERVICE_UUIDS);
+                ret.write(serviceUuids32.toByteArray(), 0, serviceUuids32.size());
+            }
+
+            if (serviceUuids128.size() != 0) {
+                ret.write(serviceUuids128.size() + 1);
+                ret.write(COMPLETE_LIST_128_BIT_SERVICE_UUIDS);
+                ret.write(serviceUuids128.toByteArray(), 0, serviceUuids128.size());
+            }
+        }
+
+        if (!data.getServiceData().isEmpty()) {
+            for (ParcelUuid parcelUuid : data.getServiceData().keySet()) {
+                byte[] serviceData = data.getServiceData().get(parcelUuid);
+
+                byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid);
+                int uuidLen = uuid.length;
+
+                int dataLen = uuidLen + (serviceData == null ? 0 : serviceData.length);
+                byte[] concated = new byte[dataLen];
+
+                System.arraycopy(uuid, 0, concated, 0, uuidLen);
+
+                if (serviceData != null) {
+                    System.arraycopy(serviceData, 0, concated, uuidLen, serviceData.length);
+                }
+
+                if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) {
+                    ret.write(concated.length + 1);
+                    ret.write(SERVICE_DATA_16_BIT_UUID);
+                    ret.write(concated, 0, concated.length);
+                } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) {
+                    ret.write(concated.length + 1);
+                    ret.write(SERVICE_DATA_32_BIT_UUID);
+                    ret.write(concated, 0, concated.length);
+                } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ {
+                    ret.write(concated.length + 1);
+                    ret.write(SERVICE_DATA_128_BIT_UUID);
+                    ret.write(concated, 0, concated.length);
+                }
+            }
+        }
+
+        return ret.toByteArray();
+    }
 }
diff --git a/src/com/android/bluetooth/gatt/AdvertiseManager.java b/src/com/android/bluetooth/gatt/AdvertiseManager.java
index f12cd89..85917a4 100644
--- a/src/com/android/bluetooth/gatt/AdvertiseManager.java
+++ b/src/com/android/bluetooth/gatt/AdvertiseManager.java
@@ -25,19 +25,14 @@
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
-import com.android.bluetooth.Utils;
+
 import com.android.bluetooth.btservice.AdapterService;
+
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Manages Bluetooth LE advertising operations and interacts with bluedroid stack. TODO: add tests.
@@ -58,7 +53,9 @@
      * Constructor of {@link AdvertiseManager}.
      */
     AdvertiseManager(GattService service, AdapterService adapterService) {
-        if (DBG) Log.d(TAG, "advertise manager created");
+        if (DBG) {
+            Log.d(TAG, "advertise manager created");
+        }
         mService = service;
         mAdapterService = adapterService;
     }
@@ -74,7 +71,9 @@
     }
 
     void cleanup() {
-        if (DBG) Log.d(TAG, "cleanup()");
+        if (DBG) {
+            Log.d(TAG, "cleanup()");
+        }
         cleanupNative();
         mAdvertisers.clear();
         sTempRegistrationId = -1;
@@ -110,23 +109,25 @@
     }
 
     class AdvertisingSetDeathRecipient implements IBinder.DeathRecipient {
-        IAdvertisingSetCallback callback;
+        public IAdvertisingSetCallback callback;
 
-        public AdvertisingSetDeathRecipient(IAdvertisingSetCallback callback) {
+        AdvertisingSetDeathRecipient(IAdvertisingSetCallback callback) {
             this.callback = callback;
         }
 
         @Override
         public void binderDied() {
-            if (DBG) Log.d(TAG, "Binder is dead - unregistering advertising set");
+            if (DBG) {
+                Log.d(TAG, "Binder is dead - unregistering advertising set");
+            }
             stopAdvertisingSet(callback);
         }
     }
 
-    Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiser_id) {
+    Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiserId) {
         Map.Entry<IBinder, AdvertiserInfo> entry = null;
         for (Map.Entry<IBinder, AdvertiserInfo> e : mAdvertisers.entrySet()) {
-            if (e.getValue().id == advertiser_id) {
+            if (e.getValue().id == advertiserId) {
                 entry = e;
                 break;
             }
@@ -134,50 +135,51 @@
         return entry;
     }
 
-    void onAdvertisingSetStarted(int reg_id, int advertiser_id, int tx_power, int status)
+    void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status)
             throws Exception {
         if (DBG) {
-            Log.d(TAG, "onAdvertisingSetStarted() - reg_id=" + reg_id + ", advertiser_id="
-                            + advertiser_id + ", status=" + status);
+            Log.d(TAG,
+                    "onAdvertisingSetStarted() - regId=" + regId + ", advertiserId=" + advertiserId
+                            + ", status=" + status);
         }
 
-        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(reg_id);
+        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(regId);
 
         if (entry == null) {
-            Log.i(TAG, "onAdvertisingSetStarted() - no callback found for reg_id " + reg_id);
+            Log.i(TAG, "onAdvertisingSetStarted() - no callback found for regId " + regId);
             // Advertising set was stopped before it was properly registered.
-            stopAdvertisingSetNative(advertiser_id);
+            stopAdvertisingSetNative(advertiserId);
             return;
         }
 
         IAdvertisingSetCallback callback = entry.getValue().callback;
         if (status == 0) {
             entry.setValue(
-                    new AdvertiserInfo(advertiser_id, entry.getValue().deathRecipient, callback));
+                    new AdvertiserInfo(advertiserId, entry.getValue().deathRecipient, callback));
         } else {
             IBinder binder = entry.getKey();
             binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
             mAdvertisers.remove(binder);
         }
 
-        callback.onAdvertisingSetStarted(advertiser_id, tx_power, status);
+        callback.onAdvertisingSetStarted(advertiserId, txPower, status);
     }
 
-    void onAdvertisingEnabled(int advertiser_id, boolean enable, int status) throws Exception {
+    void onAdvertisingEnabled(int advertiserId, boolean enable, int status) throws Exception {
         if (DBG) {
-            Log.d(TAG, "onAdvertisingSetEnabled() - advertiser_id=" + advertiser_id + ", enable="
-                            + enable + ", status=" + status);
+            Log.d(TAG, "onAdvertisingSetEnabled() - advertiserId=" + advertiserId + ", enable="
+                    + enable + ", status=" + status);
         }
 
-        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
+        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
         if (entry == null) {
-            Log.i(TAG, "onAdvertisingSetEnable() - no callback found for advertiser_id "
-                            + advertiser_id);
+            Log.i(TAG, "onAdvertisingSetEnable() - no callback found for advertiserId "
+                    + advertiserId);
             return;
         }
 
         IAdvertisingSetCallback callback = entry.getValue().callback;
-        callback.onAdvertisingEnabled(advertiser_id, enable, status);
+        callback.onAdvertisingEnabled(advertiserId, enable, status);
     }
 
     void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData,
@@ -193,30 +195,34 @@
         }
 
         String deviceName = AdapterService.getAdapterService().getName();
-        byte[] adv_data = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName);
-        byte[] scan_response = AdvertiseHelper.advertiseDataToBytes(scanResponse, deviceName);
-        byte[] periodic_data = AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName);
+        byte[] advDataBytes = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName);
+        byte[] scanResponseBytes = AdvertiseHelper.advertiseDataToBytes(scanResponse, deviceName);
+        byte[] periodicDataBytes = AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName);
 
-        int cb_id = --sTempRegistrationId;
-        mAdvertisers.put(binder, new AdvertiserInfo(cb_id, deathRecipient, callback));
+        int cbId = --sTempRegistrationId;
+        mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback));
 
-        if (DBG) Log.d(TAG, "startAdvertisingSet() - reg_id=" + cb_id + ", callback: " + binder);
-        startAdvertisingSetNative(parameters, adv_data, scan_response, periodicParameters,
-                periodic_data, duration, maxExtAdvEvents, cb_id);
+        if (DBG) {
+            Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder);
+        }
+        startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes, periodicParameters,
+                periodicDataBytes, duration, maxExtAdvEvents, cbId);
     }
 
-    void onOwnAddressRead(int advertiser_id, int addressType, String address)
+    void onOwnAddressRead(int advertiserId, int addressType, String address)
             throws RemoteException {
-        if (DBG) Log.d(TAG, "onOwnAddressRead() advertiser_id=" + advertiser_id);
+        if (DBG) {
+            Log.d(TAG, "onOwnAddressRead() advertiserId=" + advertiserId);
+        }
 
-        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
+        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
         if (entry == null) {
-            Log.i(TAG, "onOwnAddressRead() - bad advertiser_id " + advertiser_id);
+            Log.i(TAG, "onOwnAddressRead() - bad advertiserId " + advertiserId);
             return;
         }
 
         IAdvertisingSetCallback callback = entry.getValue().callback;
-        callback.onOwnAddressRead(advertiser_id, addressType, address);
+        callback.onOwnAddressRead(advertiserId, addressType, address);
     }
 
     void getOwnAddress(int advertiserId) {
@@ -225,7 +231,9 @@
 
     void stopAdvertisingSet(IAdvertisingSetCallback callback) {
         IBinder binder = toBinder(callback);
-        if (DBG) Log.d(TAG, "stopAdvertisingSet() " + binder);
+        if (DBG) {
+            Log.d(TAG, "stopAdvertisingSet() " + binder);
+        }
 
         AdvertiserInfo adv = mAdvertisers.remove(binder);
         if (adv == null) {
@@ -233,19 +241,19 @@
             return;
         }
 
-        Integer advertiser_id = adv.id;
+        Integer advertiserId = adv.id;
         binder.unlinkToDeath(adv.deathRecipient, 0);
 
-        if (advertiser_id < 0) {
+        if (advertiserId < 0) {
             Log.i(TAG, "stopAdvertisingSet() - advertiser not finished registration yet");
             // Advertiser will be freed once initiated in onAdvertisingSetStarted()
             return;
         }
 
-        stopAdvertisingSetNative(advertiser_id);
+        stopAdvertisingSetNative(advertiserId);
 
         try {
-            callback.onAdvertisingSetStopped(advertiser_id);
+            callback.onAdvertisingSetStopped(advertiserId);
         } catch (RemoteException e) {
             Log.i(TAG, "error sending onAdvertisingSetStopped callback", e);
         }
@@ -257,154 +265,168 @@
 
     void setAdvertisingData(int advertiserId, AdvertiseData data) {
         String deviceName = AdapterService.getAdapterService().getName();
-        setAdvertisingDataNative(
-                advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName));
+        setAdvertisingDataNative(advertiserId,
+                AdvertiseHelper.advertiseDataToBytes(data, deviceName));
     }
 
     void setScanResponseData(int advertiserId, AdvertiseData data) {
         String deviceName = AdapterService.getAdapterService().getName();
-        setScanResponseDataNative(
-                advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName));
+        setScanResponseDataNative(advertiserId,
+                AdvertiseHelper.advertiseDataToBytes(data, deviceName));
     }
 
     void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) {
         setAdvertisingParametersNative(advertiserId, parameters);
     }
 
-    void setPeriodicAdvertisingParameters(
-            int advertiserId, PeriodicAdvertisingParameters parameters) {
+    void setPeriodicAdvertisingParameters(int advertiserId,
+            PeriodicAdvertisingParameters parameters) {
         setPeriodicAdvertisingParametersNative(advertiserId, parameters);
     }
 
     void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
         String deviceName = AdapterService.getAdapterService().getName();
-        setPeriodicAdvertisingDataNative(
-                advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName));
+        setPeriodicAdvertisingDataNative(advertiserId,
+                AdvertiseHelper.advertiseDataToBytes(data, deviceName));
     }
 
     void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) {
         setPeriodicAdvertisingEnableNative(advertiserId, enable);
     }
 
-    void onAdvertisingDataSet(int advertiser_id, int status) throws Exception {
+    void onAdvertisingDataSet(int advertiserId, int status) throws Exception {
         if (DBG) {
             Log.d(TAG,
-                    "onAdvertisingDataSet() advertiser_id=" + advertiser_id + ", status=" + status);
+                    "onAdvertisingDataSet() advertiserId=" + advertiserId + ", status=" + status);
         }
 
-        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
+        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
         if (entry == null) {
-            Log.i(TAG, "onAdvertisingDataSet() - bad advertiser_id " + advertiser_id);
+            Log.i(TAG, "onAdvertisingDataSet() - bad advertiserId " + advertiserId);
             return;
         }
 
         IAdvertisingSetCallback callback = entry.getValue().callback;
-        callback.onAdvertisingDataSet(advertiser_id, status);
+        callback.onAdvertisingDataSet(advertiserId, status);
     }
 
-    void onScanResponseDataSet(int advertiser_id, int status) throws Exception {
-        if (DBG)
-            Log.d(TAG, "onScanResponseDataSet() advertiser_id=" + advertiser_id + ", status="
-                            + status);
+    void onScanResponseDataSet(int advertiserId, int status) throws Exception {
+        if (DBG) {
+            Log.d(TAG,
+                    "onScanResponseDataSet() advertiserId=" + advertiserId + ", status=" + status);
+        }
 
-        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
+        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
         if (entry == null) {
-            Log.i(TAG, "onScanResponseDataSet() - bad advertiser_id " + advertiser_id);
+            Log.i(TAG, "onScanResponseDataSet() - bad advertiserId " + advertiserId);
             return;
         }
 
         IAdvertisingSetCallback callback = entry.getValue().callback;
-        callback.onScanResponseDataSet(advertiser_id, status);
+        callback.onScanResponseDataSet(advertiserId, status);
     }
 
-    void onAdvertisingParametersUpdated(int advertiser_id, int tx_power, int status)
+    void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status)
             throws Exception {
         if (DBG) {
-            Log.d(TAG, "onAdvertisingParametersUpdated() advertiser_id=" + advertiser_id
-                            + ", tx_power=" + tx_power + ", status=" + status);
+            Log.d(TAG,
+                    "onAdvertisingParametersUpdated() advertiserId=" + advertiserId + ", txPower="
+                            + txPower + ", status=" + status);
         }
 
-        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
+        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
         if (entry == null) {
-            Log.i(TAG, "onAdvertisingParametersUpdated() - bad advertiser_id " + advertiser_id);
+            Log.i(TAG, "onAdvertisingParametersUpdated() - bad advertiserId " + advertiserId);
             return;
         }
 
         IAdvertisingSetCallback callback = entry.getValue().callback;
-        callback.onAdvertisingParametersUpdated(advertiser_id, tx_power, status);
+        callback.onAdvertisingParametersUpdated(advertiserId, txPower, status);
     }
 
-    void onPeriodicAdvertisingParametersUpdated(int advertiser_id, int status) throws Exception {
+    void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) throws Exception {
         if (DBG) {
-            Log.d(TAG, "onPeriodicAdvertisingParametersUpdated() advertiser_id=" + advertiser_id
-                            + ", status=" + status);
+            Log.d(TAG, "onPeriodicAdvertisingParametersUpdated() advertiserId=" + advertiserId
+                    + ", status=" + status);
         }
 
-        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
+        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
         if (entry == null) {
-            Log.i(TAG, "onPeriodicAdvertisingParametersUpdated() - bad advertiser_id "
-                            + advertiser_id);
+            Log.i(TAG,
+                    "onPeriodicAdvertisingParametersUpdated() - bad advertiserId " + advertiserId);
             return;
         }
 
         IAdvertisingSetCallback callback = entry.getValue().callback;
-        callback.onPeriodicAdvertisingParametersUpdated(advertiser_id, status);
+        callback.onPeriodicAdvertisingParametersUpdated(advertiserId, status);
     }
 
-    void onPeriodicAdvertisingDataSet(int advertiser_id, int status) throws Exception {
+    void onPeriodicAdvertisingDataSet(int advertiserId, int status) throws Exception {
         if (DBG) {
-            Log.d(TAG, "onPeriodicAdvertisingDataSet() advertiser_id=" + advertiser_id + ", status="
-                            + status);
+            Log.d(TAG, "onPeriodicAdvertisingDataSet() advertiserId=" + advertiserId + ", status="
+                    + status);
         }
 
-        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
+        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
         if (entry == null) {
-            Log.i(TAG, "onPeriodicAdvertisingDataSet() - bad advertiser_id " + advertiser_id);
+            Log.i(TAG, "onPeriodicAdvertisingDataSet() - bad advertiserId " + advertiserId);
             return;
         }
 
         IAdvertisingSetCallback callback = entry.getValue().callback;
-        callback.onPeriodicAdvertisingDataSet(advertiser_id, status);
+        callback.onPeriodicAdvertisingDataSet(advertiserId, status);
     }
 
-    void onPeriodicAdvertisingEnabled(int advertiser_id, boolean enable, int status)
+    void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status)
             throws Exception {
         if (DBG) {
-            Log.d(TAG, "onPeriodicAdvertisingEnabled() advertiser_id=" + advertiser_id + ", status="
-                            + status);
+            Log.d(TAG, "onPeriodicAdvertisingEnabled() advertiserId=" + advertiserId + ", status="
+                    + status);
         }
 
-        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
+        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
         if (entry == null) {
-            Log.i(TAG, "onAdvertisingSetEnable() - bad advertiser_id " + advertiser_id);
+            Log.i(TAG, "onAdvertisingSetEnable() - bad advertiserId " + advertiserId);
             return;
         }
 
         IAdvertisingSetCallback callback = entry.getValue().callback;
-        callback.onPeriodicAdvertisingEnabled(advertiser_id, enable, status);
+        callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status);
     }
 
     static {
         classInitNative();
     }
 
-    private native static void classInitNative();
+    private static native void classInitNative();
+
     private native void initializeNative();
+
     private native void cleanupNative();
+
     private native void startAdvertisingSetNative(AdvertisingSetParameters parameters,
             byte[] advertiseData, byte[] scanResponse,
             PeriodicAdvertisingParameters periodicParameters, byte[] periodicData, int duration,
-            int maxExtAdvEvents, int reg_id);
+            int maxExtAdvEvents, int regId);
+
     private native void getOwnAddressNative(int advertiserId);
-    private native void stopAdvertisingSetNative(int advertiser_id);
-    private native void enableAdvertisingSetNative(
-            int advertiserId, boolean enable, int duration, int maxExtAdvEvents);
+
+    private native void stopAdvertisingSetNative(int advertiserId);
+
+    private native void enableAdvertisingSetNative(int advertiserId, boolean enable, int duration,
+            int maxExtAdvEvents);
+
     private native void setAdvertisingDataNative(int advertiserId, byte[] data);
+
     private native void setScanResponseDataNative(int advertiserId, byte[] data);
-    private native void setAdvertisingParametersNative(
-            int advertiserId, AdvertisingSetParameters parameters);
-    private native void setPeriodicAdvertisingParametersNative(
-            int advertiserId, PeriodicAdvertisingParameters parameters);
+
+    private native void setAdvertisingParametersNative(int advertiserId,
+            AdvertisingSetParameters parameters);
+
+    private native void setPeriodicAdvertisingParametersNative(int advertiserId,
+            PeriodicAdvertisingParameters parameters);
+
     private native void setPeriodicAdvertisingDataNative(int advertiserId, byte[] data);
+
     private native void setPeriodicAdvertisingEnableNative(int advertiserId, boolean enable);
 }
diff --git a/src/com/android/bluetooth/gatt/AdvtFilterOnFoundOnLostInfo.java b/src/com/android/bluetooth/gatt/AdvtFilterOnFoundOnLostInfo.java
index b5e6773..c16f826 100644
--- a/src/com/android/bluetooth/gatt/AdvtFilterOnFoundOnLostInfo.java
+++ b/src/com/android/bluetooth/gatt/AdvtFilterOnFoundOnLostInfo.java
@@ -15,6 +15,7 @@
  */
 
 package com.android.bluetooth.gatt;
+
 import android.annotation.Nullable;
 
 /** @hide */
@@ -22,13 +23,11 @@
     private int mClientIf;
 
     private int mAdvPktLen;
-    @Nullable
-    private byte[] mAdvPkt;
+    @Nullable private byte[] mAdvPkt;
 
     private int mScanRspLen;
 
-    @Nullable
-    private byte[] mScanRsp;
+    @Nullable private byte[] mScanRsp;
 
     private int mFiltIndex;
     private int mAdvState;
@@ -40,51 +39,50 @@
     private int mRssiValue;
     private int mTimeStamp;
 
-    public AdvtFilterOnFoundOnLostInfo(int client_if, int adv_pkt_len, byte[] adv_pkt,
-                    int scan_rsp_len, byte[] scan_rsp, int filt_index, int adv_state,
-                    int adv_info_present, String address, int addr_type, int tx_power,
-                    int rssi_value, int time_stamp){
+    public AdvtFilterOnFoundOnLostInfo(int clientIf, int advPktLen, byte[] advPkt, int scanRspLen,
+            byte[] scanRsp, int filtIndex, int advState, int advInfoPresent, String address,
+            int addrType, int txPower, int rssiValue, int timeStamp) {
 
-        mClientIf = client_if;
-        mAdvPktLen = adv_pkt_len;
-        mAdvPkt = adv_pkt;
-        mScanRspLen = scan_rsp_len;
-        mScanRsp = scan_rsp;
-        mFiltIndex = filt_index;
-        mAdvState = adv_state;
-        mAdvInfoPresent = adv_info_present;
+        mClientIf = clientIf;
+        mAdvPktLen = advPktLen;
+        mAdvPkt = advPkt;
+        mScanRspLen = scanRspLen;
+        mScanRsp = scanRsp;
+        mFiltIndex = filtIndex;
+        mAdvState = advState;
+        mAdvInfoPresent = advInfoPresent;
         mAddress = address;
-        mAddrType = addr_type;
-        mTxPower = tx_power;
-        mRssiValue = rssi_value;
-        mTimeStamp = time_stamp;
+        mAddrType = addrType;
+        mTxPower = txPower;
+        mRssiValue = rssiValue;
+        mTimeStamp = timeStamp;
     }
 
-    public int getClientIf () {
+    public int getClientIf() {
         return mClientIf;
     }
 
-    public int getFiltIndex () {
+    public int getFiltIndex() {
         return mFiltIndex;
     }
 
-    public int getAdvState () {
+    public int getAdvState() {
         return mAdvState;
     }
 
-    public int getTxPower () {
+    public int getTxPower() {
         return mTxPower;
     }
 
-    public int getTimeStamp () {
+    public int getTimeStamp() {
         return mTimeStamp;
     }
 
-    public int getRSSIValue () {
+    public int getRSSIValue() {
         return mRssiValue;
     }
 
-    public int getAdvInfoPresent () {
+    public int getAdvInfoPresent() {
         return mAdvInfoPresent;
     }
 
@@ -112,10 +110,10 @@
         return mScanRspLen;
     }
 
-    public byte [] getResult() {
+    public byte[] getResult() {
         int resultLength = mAdvPkt.length + ((mScanRsp != null) ? mScanRsp.length : 0);
-        byte result[] = new byte[resultLength];
-        System.arraycopy(mAdvPkt, 0, result, 0,  mAdvPkt.length);
+        byte[] result = new byte[resultLength];
+        System.arraycopy(mAdvPkt, 0, result, 0, mAdvPkt.length);
         if (mScanRsp != null) {
             System.arraycopy(mScanRsp, 0, result, mAdvPkt.length, mScanRsp.length);
         }
diff --git a/src/com/android/bluetooth/gatt/AppScanStats.java b/src/com/android/bluetooth/gatt/AppScanStats.java
index e65ad6b..dd499f2 100644
--- a/src/com/android/bluetooth/gatt/AppScanStats.java
+++ b/src/com/android/bluetooth/gatt/AppScanStats.java
@@ -17,51 +17,54 @@
 
 import android.bluetooth.le.ScanSettings;
 import android.os.Binder;
-import android.os.WorkSource;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.RemoteException;
+import android.os.WorkSource;
+import android.util.StatsLog;
+
+import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.internal.app.IBatteryStats;
+
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.HashMap;
 
-import com.android.bluetooth.btservice.BluetoothProto;
 /**
  * ScanStats class helps keep track of information about scans
  * on a per application basis.
  * @hide
  */
 /*package*/ class AppScanStats {
-    static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+    private static final String TAG = AppScanStats.class.getSimpleName();
 
-    /* ContextMap here is needed to grab Apps and Connections */
-    ContextMap contextMap;
+    static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss");
 
-    /* GattService is needed to add scan event protos to be dumped later */
-    GattService gattService;
+    /* ContextMap here is needed to grab Apps and Connections */ ContextMap mContextMap;
 
-    /* Battery stats is used to keep track of scans and result stats */
-    IBatteryStats batteryStats;
+    /* GattService is needed to add scan event protos to be dumped later */ GattService
+            mGattService;
+
+    /* Battery stats is used to keep track of scans and result stats */ IBatteryStats mBatteryStats;
 
     class LastScan {
-        long duration;
-        long suspendDuration;
-        long suspendStartTime;
-        boolean isSuspended;
-        long timestamp;
-        boolean opportunistic;
-        boolean timeout;
-        boolean background;
-        boolean filtered;
-        int results;
-        int scannerId;
+        public long duration;
+        public long suspendDuration;
+        public long suspendStartTime;
+        public boolean isSuspended;
+        public long timestamp;
+        public boolean opportunistic;
+        public boolean timeout;
+        public boolean background;
+        public boolean filtered;
+        public int results;
+        public int scannerId;
 
-        public LastScan(long timestamp, long duration, boolean opportunistic, boolean background,
+        LastScan(long timestamp, long duration, boolean opportunistic, boolean background,
                 boolean filtered, int scannerId) {
             this.duration = duration;
             this.timestamp = timestamp;
@@ -87,33 +90,33 @@
     // Maximum msec before scan gets downgraded to opportunistic
     static final int SCAN_TIMEOUT_MS = 30 * 60 * 1000;
 
-    String appName;
-    WorkSource workSource; // Used for BatteryStats
-    int scansStarted = 0;
-    int scansStopped = 0;
-    boolean isRegistered = false;
-    long minScanTime = Long.MAX_VALUE;
-    long maxScanTime = 0;
-    long mScanStartTime = 0;
-    long mTotalScanTime = 0;
-    long mTotalSuspendTime = 0;
-    List<LastScan> lastScans = new ArrayList<LastScan>(NUM_SCAN_DURATIONS_KEPT);
-    HashMap<Integer, LastScan> ongoingScans = new HashMap<Integer, LastScan>();
-    long startTime = 0;
-    long stopTime = 0;
-    int results = 0;
+    public String appName;
+    public WorkSource mWorkSource; // Used for BatteryStats and StatsLog
+    private int mScansStarted = 0;
+    private int mScansStopped = 0;
+    public boolean isRegistered = false;
+    private long mMinScanTime = Long.MAX_VALUE;
+    private long mMaxScanTime = 0;
+    private long mScanStartTime = 0;
+    private long mTotalScanTime = 0;
+    private long mTotalSuspendTime = 0;
+    private List<LastScan> mLastScans = new ArrayList<LastScan>(NUM_SCAN_DURATIONS_KEPT);
+    private HashMap<Integer, LastScan> mOngoingScans = new HashMap<Integer, LastScan>();
+    public long startTime = 0;
+    public long stopTime = 0;
+    public int results = 0;
 
-    public AppScanStats(String name, WorkSource source, ContextMap map, GattService service) {
+    AppScanStats(String name, WorkSource source, ContextMap map, GattService service) {
         appName = name;
-        contextMap = map;
-        gattService = service;
-        batteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batterystats"));
+        mContextMap = map;
+        mGattService = service;
+        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batterystats"));
 
         if (source == null) {
             // Bill the caller if the work source isn't passed through
             source = new WorkSource(Binder.getCallingUid(), appName);
         }
-        workSource = source;
+        mWorkSource = source;
     }
 
     synchronized void addResult(int scannerId) {
@@ -125,7 +128,7 @@
             // to lower the cost of the binder transaction
             if (batteryStatsResults % 100 == 0) {
                 try {
-                    batteryStats.noteBleScanResults(workSource, 100);
+                    mBatteryStats.noteBleScanResults(mWorkSource, 100);
                 } catch (RemoteException e) {
                     /* ignore */
                 }
@@ -136,11 +139,11 @@
     }
 
     boolean isScanning() {
-        return !ongoingScans.isEmpty();
+        return !mOngoingScans.isEmpty();
     }
 
     LastScan getScanFromScannerId(int scannerId) {
-        return ongoingScans.get(scannerId);
+        return mOngoingScans.get(scannerId);
     }
 
     synchronized void recordScanStart(ScanSettings settings, boolean filtered, int scannerId) {
@@ -148,31 +151,36 @@
         if (existingScan != null) {
             return;
         }
-        this.scansStarted++;
+        this.mScansStarted++;
         startTime = SystemClock.elapsedRealtime();
 
         LastScan scan = new LastScan(startTime, 0, false, false, filtered, scannerId);
         if (settings != null) {
-          scan.opportunistic = settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
-          scan.background = (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
+            scan.opportunistic = settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
+            scan.background =
+                    (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
         }
 
-        BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
-        scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_START);
-        scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
-        scanEvent.setEventTimeMillis(System.currentTimeMillis());
-        scanEvent.setInitiator(truncateAppName(appName));
-        gattService.addScanEvent(scanEvent);
+        BluetoothMetricsProto.ScanEvent scanEvent = BluetoothMetricsProto.ScanEvent.newBuilder()
+                .setScanEventType(BluetoothMetricsProto.ScanEvent.ScanEventType.SCAN_EVENT_START)
+                .setScanTechnologyType(
+                        BluetoothMetricsProto.ScanEvent.ScanTechnologyType.SCAN_TECH_TYPE_LE)
+                .setEventTimeMillis(System.currentTimeMillis())
+                .setInitiator(truncateAppName(appName)).build();
+        mGattService.addScanEvent(scanEvent);
 
-        if (!isScanning()) mScanStartTime = startTime;
+        if (!isScanning()) {
+            mScanStartTime = startTime;
+        }
         try {
             boolean isUnoptimized = !(scan.filtered || scan.background || scan.opportunistic);
-            batteryStats.noteBleScanStarted(workSource, isUnoptimized);
+            mBatteryStats.noteBleScanStarted(mWorkSource, isUnoptimized);
         } catch (RemoteException e) {
             /* ignore */
         }
+        writeToStatsLog(scan, StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON);
 
-        ongoingScans.put(scannerId, scan);
+        mOngoingScans.put(scannerId, scan);
     }
 
     synchronized void recordScanStop(int scannerId) {
@@ -180,43 +188,48 @@
         if (scan == null) {
             return;
         }
-        this.scansStopped++;
+        this.mScansStopped++;
         stopTime = SystemClock.elapsedRealtime();
         long scanDuration = stopTime - scan.timestamp;
         scan.duration = scanDuration;
         if (scan.isSuspended) {
-            scan.suspendDuration += stopTime - scan.suspendStartTime;
-            mTotalSuspendTime += scan.suspendDuration;
+            long suspendDuration = stopTime - scan.suspendStartTime;
+            scan.suspendDuration += suspendDuration;
+            mTotalSuspendTime += suspendDuration;
         }
-        ongoingScans.remove(scannerId);
-        if (lastScans.size() >= NUM_SCAN_DURATIONS_KEPT) {
-            lastScans.remove(0);
+        mOngoingScans.remove(scannerId);
+        if (mLastScans.size() >= NUM_SCAN_DURATIONS_KEPT) {
+            mLastScans.remove(0);
         }
-        lastScans.add(scan);
+        mLastScans.add(scan);
 
-        BluetoothProto.ScanEvent scanEvent = new BluetoothProto.ScanEvent();
-        scanEvent.setScanEventType(BluetoothProto.ScanEvent.SCAN_EVENT_STOP);
-        scanEvent.setScanTechnologyType(BluetoothProto.ScanEvent.SCAN_TECH_TYPE_LE);
-        scanEvent.setEventTimeMillis(System.currentTimeMillis());
-        scanEvent.setInitiator(truncateAppName(appName));
-        gattService.addScanEvent(scanEvent);
+        BluetoothMetricsProto.ScanEvent scanEvent = BluetoothMetricsProto.ScanEvent.newBuilder()
+                .setScanEventType(BluetoothMetricsProto.ScanEvent.ScanEventType.SCAN_EVENT_STOP)
+                .setScanTechnologyType(
+                        BluetoothMetricsProto.ScanEvent.ScanTechnologyType.SCAN_TECH_TYPE_LE)
+                .setEventTimeMillis(System.currentTimeMillis())
+                .setInitiator(truncateAppName(appName))
+                .setNumberResults(scan.results)
+                .build();
+        mGattService.addScanEvent(scanEvent);
 
         if (!isScanning()) {
             long totalDuration = stopTime - mScanStartTime;
             mTotalScanTime += totalDuration;
-            minScanTime = Math.min(totalDuration, minScanTime);
-            maxScanTime = Math.max(totalDuration, maxScanTime);
+            mMinScanTime = Math.min(totalDuration, mMinScanTime);
+            mMaxScanTime = Math.max(totalDuration, mMaxScanTime);
         }
 
         try {
             // Inform battery stats of any results it might be missing on
             // scan stop
             boolean isUnoptimized = !(scan.filtered || scan.background || scan.opportunistic);
-            batteryStats.noteBleScanResults(workSource, scan.results % 100);
-            batteryStats.noteBleScanStopped(workSource, isUnoptimized);
+            mBatteryStats.noteBleScanResults(mWorkSource, scan.results % 100);
+            mBatteryStats.noteBleScanStopped(mWorkSource, isUnoptimized);
         } catch (RemoteException e) {
             /* ignore */
         }
+        writeToStatsLog(scan, StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF);
     }
 
     synchronized void recordScanSuspend(int scannerId) {
@@ -230,17 +243,39 @@
 
     synchronized void recordScanResume(int scannerId) {
         LastScan scan = getScanFromScannerId(scannerId);
+        long suspendDuration = 0;
         if (scan == null || !scan.isSuspended) {
             return;
         }
         scan.isSuspended = false;
         stopTime = SystemClock.elapsedRealtime();
-        scan.suspendDuration += stopTime - scan.suspendStartTime;
-        mTotalSuspendTime += scan.suspendDuration;
+        suspendDuration = stopTime - scan.suspendStartTime;
+        scan.suspendDuration += suspendDuration;
+        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;
+        if (!isScanning()) {
+            return;
+        }
 
         LastScan scan = getScanFromScannerId(scannerId);
         if (scan != null) {
@@ -249,11 +284,11 @@
     }
 
     synchronized boolean isScanningTooFrequently() {
-        if (lastScans.size() < NUM_SCAN_DURATIONS_KEPT) {
+        if (mLastScans.size() < NUM_SCAN_DURATIONS_KEPT) {
             return false;
         }
 
-        return (SystemClock.elapsedRealtime() - lastScans.get(0).timestamp)
+        return (SystemClock.elapsedRealtime() - mLastScans.get(0).timestamp)
                 < EXCESSIVE_SCANNING_PERIOD_MS;
     }
 
@@ -274,9 +309,7 @@
         String initiator = name;
         String[] nameSplit = initiator.split("\\.");
         if (nameSplit.length > 3) {
-            initiator = nameSplit[0] + "." +
-                        nameSplit[1] + "." +
-                        nameSplit[2];
+            initiator = nameSplit[0] + "." + nameSplit[1] + "." + nameSplit[2];
         } else if (nameSplit.length == 3) {
             initiator = nameSplit[0] + "." + nameSplit[1];
         }
@@ -286,8 +319,8 @@
 
     synchronized void dumpToString(StringBuilder sb) {
         long currTime = SystemClock.elapsedRealtime();
-        long maxScan = maxScanTime;
-        long minScan = minScanTime;
+        long maxScan = mMaxScanTime;
+        long minScan = mMinScanTime;
         long scanDuration = 0;
 
         if (isScanning()) {
@@ -306,102 +339,122 @@
          * */
         long avgScan = 0;
         long totalScanTime = mTotalScanTime + scanDuration;
-        if (scansStarted > 0) {
-            avgScan = totalScanTime / scansStarted;
+        if (mScansStarted > 0) {
+            avgScan = totalScanTime / mScansStarted;
         }
 
         sb.append("  " + appName);
-        if (isRegistered) sb.append(" (Registered)");
+        if (isRegistered) {
+            sb.append(" (Registered)");
+        }
 
-        if (!lastScans.isEmpty()) {
-            LastScan lastScan = lastScans.get(lastScans.size() - 1);
-            if (lastScan.opportunistic) sb.append(" (Opportunistic)");
-            if (lastScan.background) sb.append(" (Background)");
-            if (lastScan.timeout) sb.append(" (Forced-Opportunistic)");
-            if (lastScan.filtered) sb.append(" (Filtered)");
+        if (!mLastScans.isEmpty()) {
+            LastScan lastScan = mLastScans.get(mLastScans.size() - 1);
+            if (lastScan.opportunistic) {
+                sb.append(" (Opportunistic)");
+            }
+            if (lastScan.background) {
+                sb.append(" (Background)");
+            }
+            if (lastScan.timeout) {
+                sb.append(" (Forced-Opportunistic)");
+            }
+            if (lastScan.filtered) {
+                sb.append(" (Filtered)");
+            }
         }
         sb.append("\n");
 
-        sb.append("  LE scans (started/stopped)         : " +
-                  scansStarted + " / " +
-                  scansStopped + "\n");
-        sb.append("  Scan time in ms (min/max/avg/total): " +
-                  minScan + " / " +
-                  maxScan + " / " +
-                  avgScan + " / " +
-                  totalScanTime + "\n");
+        sb.append("  LE scans (started/stopped)         : " + mScansStarted + " / " + mScansStopped
+                + "\n");
+        sb.append("  Scan time in ms (min/max/avg/total): " + minScan + " / " + maxScan + " / "
+                + avgScan + " / " + totalScanTime + "\n");
         if (mTotalSuspendTime != 0) {
-            sb.append("  Total time suspended             : " + mTotalSuspendTime + "ms\n");
+            sb.append("  Total time suspended               : " + mTotalSuspendTime + "ms\n");
         }
-        sb.append("  Total number of results            : " +
-                  results + "\n");
+        sb.append("  Total number of results            : " + results + "\n");
 
         long currentTime = System.currentTimeMillis();
         long elapsedRt = SystemClock.elapsedRealtime();
-        if (!lastScans.isEmpty()) {
-            sb.append("  Last " + lastScans.size() + " scans                       :\n");
+        if (!mLastScans.isEmpty()) {
+            sb.append("  Last " + mLastScans.size() + " scans                       :\n");
 
-            for (int i = 0; i < lastScans.size(); i++) {
-                LastScan scan = lastScans.get(i);
+            for (int i = 0; i < mLastScans.size(); i++) {
+                LastScan scan = mLastScans.get(i);
                 Date timestamp = new Date(currentTime - elapsedRt + scan.timestamp);
-                sb.append("    " + dateFormat.format(timestamp) + " - ");
+                sb.append("    " + DATE_FORMAT.format(timestamp) + " - ");
                 sb.append(scan.duration + "ms ");
-                if (scan.opportunistic) sb.append("Opp ");
-                if (scan.background) sb.append("Back ");
-                if (scan.timeout) sb.append("Forced ");
-                if (scan.filtered) sb.append("Filter ");
+                if (scan.opportunistic) {
+                    sb.append("Opp ");
+                }
+                if (scan.background) {
+                    sb.append("Back ");
+                }
+                if (scan.timeout) {
+                    sb.append("Forced ");
+                }
+                if (scan.filtered) {
+                    sb.append("Filter ");
+                }
                 sb.append(scan.results + " results");
                 sb.append(" (" + scan.scannerId + ")");
                 sb.append("\n");
                 if (scan.suspendDuration != 0) {
-                    sb.append("      └"
-                            + " Suspended Time: " + scan.suspendDuration + "ms\n");
+                    sb.append("      └" + " Suspended Time: " + scan.suspendDuration + "ms\n");
                 }
             }
         }
 
-        if (!ongoingScans.isEmpty()) {
+        if (!mOngoingScans.isEmpty()) {
             sb.append("  Ongoing scans                      :\n");
-            for (Integer key : ongoingScans.keySet()) {
-                LastScan scan = ongoingScans.get(key);
+            for (Integer key : mOngoingScans.keySet()) {
+                LastScan scan = mOngoingScans.get(key);
                 Date timestamp = new Date(currentTime - elapsedRt + scan.timestamp);
-                sb.append("    " + dateFormat.format(timestamp) + " - ");
+                sb.append("    " + DATE_FORMAT.format(timestamp) + " - ");
                 sb.append((elapsedRt - scan.timestamp) + "ms ");
-                if (scan.opportunistic) sb.append("Opp ");
-                if (scan.background) sb.append("Back ");
-                if (scan.timeout) sb.append("Forced ");
-                if (scan.filtered) sb.append("Filter ");
-                if (scan.isSuspended) sb.append("Suspended ");
+                if (scan.opportunistic) {
+                    sb.append("Opp ");
+                }
+                if (scan.background) {
+                    sb.append("Back ");
+                }
+                if (scan.timeout) {
+                    sb.append("Forced ");
+                }
+                if (scan.filtered) {
+                    sb.append("Filter ");
+                }
+                if (scan.isSuspended) {
+                    sb.append("Suspended ");
+                }
                 sb.append(scan.results + " results");
                 sb.append(" (" + scan.scannerId + ")");
                 sb.append("\n");
                 if (scan.suspendStartTime != 0) {
-                    long duration = scan.suspendDuration
-                            + (scan.isSuspended ? (elapsedRt - scan.suspendStartTime) : 0);
-                    sb.append("      └"
-                            + " Suspended Time: " + duration + "ms\n");
+                    long duration = scan.suspendDuration + (scan.isSuspended ? (elapsedRt
+                            - scan.suspendStartTime) : 0);
+                    sb.append("      └" + " Suspended Time: " + duration + "ms\n");
                 }
             }
         }
 
-        ContextMap.App appEntry = contextMap.getByName(appName);
+        ContextMap.App appEntry = mContextMap.getByName(appName);
         if (appEntry != null && isRegistered) {
-            sb.append("  Application ID                     : " +
-                      appEntry.id + "\n");
-            sb.append("  UUID                               : " +
-                      appEntry.uuid + "\n");
+            sb.append("  Application ID                     : " + appEntry.id + "\n");
+            sb.append("  UUID                               : " + appEntry.uuid + "\n");
 
-            List<ContextMap.Connection> connections =
-              contextMap.getConnectionByApp(appEntry.id);
+            List<ContextMap.Connection> connections = mContextMap.getConnectionByApp(appEntry.id);
 
             sb.append("  Connections: " + connections.size() + "\n");
 
             Iterator<ContextMap.Connection> ii = connections.iterator();
-            while(ii.hasNext()) {
+            while (ii.hasNext()) {
                 ContextMap.Connection connection = ii.next();
-                long connectionTime = SystemClock.elapsedRealtime() - connection.startTime;
-                sb.append("    " + connection.connId + ": " +
-                          connection.address + " " + connectionTime + "ms\n");
+                long connectionTime = elapsedRt - connection.startTime;
+                Date timestamp = new Date(currentTime - elapsedRt + connection.startTime);
+                sb.append("    " + DATE_FORMAT.format(timestamp) + " - ");
+                sb.append((connectionTime) + "ms ");
+                sb.append(": " + connection.address + " (" + connection.connId + ")\n");
             }
         }
         sb.append("\n");
diff --git a/src/com/android/bluetooth/gatt/CallbackInfo.java b/src/com/android/bluetooth/gatt/CallbackInfo.java
index 3b23d71..330422d 100644
--- a/src/com/android/bluetooth/gatt/CallbackInfo.java
+++ b/src/com/android/bluetooth/gatt/CallbackInfo.java
@@ -23,9 +23,9 @@
 /*package*/
 
 class CallbackInfo {
-    String address;
-    int status;
-    int handle;
+    public String address;
+    public int status;
+    public int handle;
 
     CallbackInfo(String address, int status, int handle) {
         this.address = address;
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
index 13c46a8..af59262 100644
--- a/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -17,22 +17,21 @@
 
 import android.os.Binder;
 import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
 import android.os.IInterface;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.WorkSource;
 import android.util.Log;
+
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.UUID;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.android.bluetooth.btservice.BluetoothProto;
 
 /**
  * Helper class that keeps track of registered GATT applications.
@@ -46,16 +45,16 @@
      * Connection class helps map connection IDs to device addresses.
      */
     class Connection {
-        int connId;
-        String address;
-        int appId;
-        long startTime;
+        public int connId;
+        public String address;
+        public int appId;
+        public long startTime;
 
-        Connection(int connId, String address,int appId) {
+        Connection(int connId, String address, int appId) {
             this.connId = connId;
             this.address = address;
             this.appId = appId;
-            this.startTime = System.currentTimeMillis();
+            this.startTime = SystemClock.elapsedRealtime();
         }
     }
 
@@ -64,27 +63,27 @@
      */
     class App {
         /** The UUID of the application */
-        UUID uuid;
+        public UUID uuid;
 
         /** The id of the application */
-        int id;
+        public int id;
 
         /** The package name of the application */
-        String name;
+        public String name;
 
         /** Statistics for this app */
-        AppScanStats appScanStats;
+        public AppScanStats appScanStats;
 
         /** Application callbacks */
-        C callback;
+        public C callback;
 
         /** Context information */
-        T info;
+        public T info;
         /** Death receipient */
         private IBinder.DeathRecipient mDeathRecipient;
 
         /** Flag to signal that transport is congested */
-        Boolean isCongested = false;
+        public Boolean isCongested = false;
 
         /** Whether the calling app has location permission */
         boolean hasLocationPermisson;
@@ -93,7 +92,7 @@
         boolean hasPeersMacAddressPermission;
 
         /** Internal callback info queue, waiting to be send on congestion clear */
-        private List<CallbackInfo> congestionQueue = new ArrayList<CallbackInfo>();
+        private List<CallbackInfo> mCongestionQueue = new ArrayList<CallbackInfo>();
 
         /**
          * Creates a new app context.
@@ -111,9 +110,11 @@
          */
         void linkToDeath(IBinder.DeathRecipient deathRecipient) {
             // It might not be a binder object
-            if (callback == null) return;
+            if (callback == null) {
+                return;
+            }
             try {
-                IBinder binder = ((IInterface)callback).asBinder();
+                IBinder binder = ((IInterface) callback).asBinder();
                 binder.linkToDeath(deathRecipient, 0);
                 mDeathRecipient = deathRecipient;
             } catch (RemoteException e) {
@@ -127,8 +128,8 @@
         void unlinkToDeath() {
             if (mDeathRecipient != null) {
                 try {
-                    IBinder binder = ((IInterface)callback).asBinder();
-                    binder.unlinkToDeath(mDeathRecipient,0);
+                    IBinder binder = ((IInterface) callback).asBinder();
+                    binder.unlinkToDeath(mDeathRecipient, 0);
                 } catch (NoSuchElementException e) {
                     Log.e(TAG, "Unable to unlink deathRecipient for app id " + id);
                 }
@@ -136,12 +137,14 @@
         }
 
         void queueCallback(CallbackInfo callbackInfo) {
-            congestionQueue.add(callbackInfo);
+            mCongestionQueue.add(callbackInfo);
         }
 
         CallbackInfo popQueuedCallback() {
-            if (congestionQueue.size() == 0) return null;
-            return congestionQueue.remove(0);
+            if (mCongestionQueue.size() == 0) {
+                return null;
+            }
+            return mCongestionQueue.remove(0);
         }
     }
 
@@ -275,7 +278,9 @@
             Iterator<App> i = mApps.iterator();
             while (i.hasNext()) {
                 App entry = i.next();
-                if (entry.id == id) return entry;
+                if (entry.id == id) {
+                    return entry;
+                }
             }
         }
         Log.e(TAG, "Context not found for ID " + id);
@@ -290,7 +295,9 @@
             Iterator<App> i = mApps.iterator();
             while (i.hasNext()) {
                 App entry = i.next();
-                if (entry.uuid.equals(uuid)) return entry;
+                if (entry.uuid.equals(uuid)) {
+                    return entry;
+                }
             }
         }
         Log.e(TAG, "Context not found for UUID " + uuid);
@@ -305,7 +312,9 @@
             Iterator<App> i = mApps.iterator();
             while (i.hasNext()) {
                 App entry = i.next();
-                if (entry.name.equals(name)) return entry;
+                if (entry.name.equals(name)) {
+                    return entry;
+                }
             }
         }
         Log.e(TAG, "Context not found for name " + name);
@@ -367,7 +376,7 @@
         Iterator<Connection> ii = mConnections.iterator();
         while (ii.hasNext()) {
             Connection connection = ii.next();
-            if (connection.connId == connId){
+            if (connection.connId == connId) {
                 return getById(connection.appId);
             }
         }
@@ -379,13 +388,16 @@
      */
     Integer connIdByAddress(int id, String address) {
         App entry = getById(id);
-        if (entry == null) return null;
+        if (entry == null) {
+            return null;
+        }
 
         Iterator<Connection> i = mConnections.iterator();
         while (i.hasNext()) {
             Connection connection = i.next();
-            if (connection.address.equalsIgnoreCase(address) && connection.appId == id)
+            if (connection.address.equalsIgnoreCase(address) && connection.appId == id) {
                 return connection.connId;
+            }
         }
         return null;
     }
@@ -397,7 +409,9 @@
         Iterator<Connection> i = mConnections.iterator();
         while (i.hasNext()) {
             Connection connection = i.next();
-            if (connection.connId == connId) return connection.address;
+            if (connection.connId == connId) {
+                return connection.address;
+            }
         }
         return null;
     }
@@ -407,8 +421,9 @@
         Iterator<Connection> i = mConnections.iterator();
         while (i.hasNext()) {
             Connection connection = i.next();
-            if (connection.appId == appId)
+            if (connection.appId == appId) {
                 currentConnections.add(connection);
+            }
         }
         return currentConnections;
     }
@@ -435,9 +450,9 @@
     /**
      * Returns connect device map with addr and appid
      */
-    Map<Integer, String> getConnectedMap(){
+    Map<Integer, String> getConnectedMap() {
         Map<Integer, String> connectedmap = new HashMap<Integer, String>();
-        for(Connection conn: mConnections){
+        for (Connection conn : mConnections) {
             connectedmap.put(conn.appId, conn.address);
         }
         return connectedmap;
diff --git a/src/com/android/bluetooth/gatt/FilterParams.java b/src/com/android/bluetooth/gatt/FilterParams.java
index db85303..64fcf9b 100644
--- a/src/com/android/bluetooth/gatt/FilterParams.java
+++ b/src/com/android/bluetooth/gatt/FilterParams.java
@@ -31,71 +31,69 @@
     private int mFoundTimeOutCnt;
     private int mNumOfTrackEntries;
 
-    public FilterParams(int client_if, int filt_index,
-        int feat_seln, int list_logic_type, int filt_logic_type,
-        int rssi_high_thres, int rssi_low_thres, int dely_mode,
-        int found_timeout, int lost_timeout, int found_timeout_cnt,
-        int num_of_tracking_entries) {
+    public FilterParams(int clientIf, int filtIndex, int featSeln, int listLogicType,
+            int filtLogicType, int rssiHighThres, int rssiLowThres, int delyMode, int foundTimeout,
+            int lostTimeout, int foundTimeoutCnt, int numOfTrackingEntries) {
 
-        mClientIf = client_if;
-        mFiltIndex = filt_index;
-        mFeatSeln = feat_seln;
-        mListLogicType = list_logic_type;
-        mFiltLogicType = filt_logic_type;
-        mRssiHighValue = rssi_high_thres;
-        mRssiLowValue = rssi_low_thres;
-        mDelyMode = dely_mode;
-        mFoundTimeOut = found_timeout;
-        mLostTimeOut = lost_timeout;
-        mFoundTimeOutCnt = found_timeout_cnt;
-        mNumOfTrackEntries = num_of_tracking_entries;
+        mClientIf = clientIf;
+        mFiltIndex = filtIndex;
+        mFeatSeln = featSeln;
+        mListLogicType = listLogicType;
+        mFiltLogicType = filtLogicType;
+        mRssiHighValue = rssiHighThres;
+        mRssiLowValue = rssiLowThres;
+        mDelyMode = delyMode;
+        mFoundTimeOut = foundTimeout;
+        mLostTimeOut = lostTimeout;
+        mFoundTimeOutCnt = foundTimeoutCnt;
+        mNumOfTrackEntries = numOfTrackingEntries;
     }
 
-    public int getClientIf () {
+    public int getClientIf() {
         return mClientIf;
     }
 
-    public int getFiltIndex () {
+    public int getFiltIndex() {
         return mFiltIndex;
     }
 
-    public int getFeatSeln () {
+    public int getFeatSeln() {
         return mFeatSeln;
     }
 
-    public int getDelyMode () {
+    public int getDelyMode() {
         return mDelyMode;
     }
 
-    public int getListLogicType () {
+    public int getListLogicType() {
         return mListLogicType;
     }
 
-    public int getFiltLogicType () {
+    public int getFiltLogicType() {
         return mFiltLogicType;
     }
 
-    public int getRSSIHighValue () {
+    public int getRSSIHighValue() {
         return mRssiHighValue;
     }
 
-    public int getRSSILowValue () {
+    public int getRSSILowValue() {
         return mRssiLowValue;
     }
 
-    public int getFoundTimeout () {
+    public int getFoundTimeout() {
         return mFoundTimeOut;
     }
 
-    public int getFoundTimeOutCnt () {
+    public int getFoundTimeOutCnt() {
         return mFoundTimeOutCnt;
     }
 
-    public int getLostTimeout () {
+    public int getLostTimeout() {
         return mLostTimeOut;
     }
 
-    public int getNumOfTrackEntries () {
+    public int getNumOfTrackEntries() {
         return mNumOfTrackEntries;
     }
 
diff --git a/src/com/android/bluetooth/gatt/GattDebugUtils.java b/src/com/android/bluetooth/gatt/GattDebugUtils.java
index a1b37a2..232477b 100644
--- a/src/com/android/bluetooth/gatt/GattDebugUtils.java
+++ b/src/com/android/bluetooth/gatt/GattDebugUtils.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
+
 import java.util.UUID;
 
 /**
@@ -29,18 +30,17 @@
     private static final boolean DEBUG_ADMIN = GattServiceConfig.DEBUG_ADMIN;
 
     private static final String ACTION_GATT_PAIRING_CONFIG =
-                                "android.bluetooth.action.GATT_PAIRING_CONFIG";
+            "android.bluetooth.action.GATT_PAIRING_CONFIG";
 
-    private static final String ACTION_GATT_TEST_USAGE =
-                                "android.bluetooth.action.GATT_TEST_USAGE";
+    private static final String ACTION_GATT_TEST_USAGE = "android.bluetooth.action.GATT_TEST_USAGE";
     private static final String ACTION_GATT_TEST_ENABLE =
-                                "android.bluetooth.action.GATT_TEST_ENABLE";
+            "android.bluetooth.action.GATT_TEST_ENABLE";
     private static final String ACTION_GATT_TEST_CONNECT =
-                                "android.bluetooth.action.GATT_TEST_CONNECT";
+            "android.bluetooth.action.GATT_TEST_CONNECT";
     private static final String ACTION_GATT_TEST_DISCONNECT =
-                                "android.bluetooth.action.GATT_TEST_DISCONNECT";
+            "android.bluetooth.action.GATT_TEST_DISCONNECT";
     private static final String ACTION_GATT_TEST_DISCOVER =
-                                "android.bluetooth.action.GATT_TEST_DISCOVER";
+            "android.bluetooth.action.GATT_TEST_DISCOVER";
 
     private static final String EXTRA_ENABLE = "enable";
     private static final String EXTRA_ADDRESS = "address";
@@ -69,7 +69,9 @@
      *   import com.android.bluetooth.gatt.GattService;
      */
     static boolean handleDebugAction(GattService svc, Intent intent) {
-        if (!DEBUG_ADMIN) return false;
+        if (!DEBUG_ADMIN) {
+            return false;
+        }
 
         String action = intent.getAction();
         Log.d(TAG, "handleDebugAction() action=" + action);
@@ -83,32 +85,31 @@
 
         } else if (ACTION_GATT_TEST_ENABLE.equals(action)) {
             boolean bEnable = intent.getBooleanExtra(EXTRA_ENABLE, true);
-            svc.gattTestCommand( 0x01, null,null, bEnable ? 1 : 0, 0,0,0,0);
+            svc.gattTestCommand(0x01, null, null, bEnable ? 1 : 0, 0, 0, 0, 0);
 
         } else if (ACTION_GATT_TEST_CONNECT.equals(action)) {
             String address = intent.getStringExtra(EXTRA_ADDRESS);
             int type = intent.getIntExtra(EXTRA_TYPE, 2 /* LE device */);
-            int addr_type = intent.getIntExtra(EXTRA_ADDR_TYPE, 0 /* Static */);
-            svc.gattTestCommand( 0x02, null, address, type, addr_type, 0,0,0);
+            int addrType = intent.getIntExtra(EXTRA_ADDR_TYPE, 0 /* Static */);
+            svc.gattTestCommand(0x02, null, address, type, addrType, 0, 0, 0);
 
         } else if (ACTION_GATT_TEST_DISCONNECT.equals(action)) {
-            svc.gattTestCommand( 0x03, null, null, 0,0,0,0,0);
+            svc.gattTestCommand(0x03, null, null, 0, 0, 0, 0, 0);
 
         } else if (ACTION_GATT_TEST_DISCOVER.equals(action)) {
             UUID uuid = getUuidExtra(intent);
             int type = intent.getIntExtra(EXTRA_TYPE, 1 /* All services */);
             int shdl = getHandleExtra(intent, EXTRA_SHANDLE, 1);
             int ehdl = getHandleExtra(intent, EXTRA_EHANDLE, 0xFFFF);
-            svc.gattTestCommand( 0x04, uuid, null, type, shdl, ehdl, 0,0);
+            svc.gattTestCommand(0x04, uuid, null, type, shdl, ehdl, 0, 0);
 
         } else if (ACTION_GATT_PAIRING_CONFIG.equals(action)) {
-            int auth_req = intent.getIntExtra(EXTRA_AUTH_REQ, 5);
-            int io_cap = intent.getIntExtra(EXTRA_IO_CAP, 4);
-            int init_key = intent.getIntExtra(EXTRA_INIT_KEY, 7);
-            int resp_key = intent.getIntExtra(EXTRA_RESP_KEY, 7);
-            int max_key = intent.getIntExtra(EXTRA_MAX_KEY, 16);
-            svc.gattTestCommand( 0xF0, null, null, auth_req, io_cap, init_key,
-                                 resp_key, max_key);
+            int authReq = intent.getIntExtra(EXTRA_AUTH_REQ, 5);
+            int ioCap = intent.getIntExtra(EXTRA_IO_CAP, 4);
+            int initKey = intent.getIntExtra(EXTRA_INIT_KEY, 7);
+            int respKey = intent.getIntExtra(EXTRA_RESP_KEY, 7);
+            int maxKey = intent.getIntExtra(EXTRA_MAX_KEY, 16);
+            svc.gattTestCommand(0xF0, null, null, authReq, ioCap, initKey, respKey, maxKey);
 
         } else {
             return false;
@@ -122,19 +123,18 @@
      * then as an ineger.
      * @hide
      */
-    static private int getHandleExtra(Intent intent, String extra, int default_value) {
+    private static int getHandleExtra(Intent intent, String extra, int defaultValue) {
         Bundle extras = intent.getExtras();
         Object uuid = extras != null ? extras.get(extra) : null;
         if (uuid != null && uuid.getClass().getName().equals("java.lang.String")) {
-            try
-            {
+            try {
                 return Integer.parseInt(extras.getString(extra), 16);
             } catch (NumberFormatException e) {
-                return default_value;
+                return defaultValue;
             }
         }
 
-        return intent.getIntExtra(extra, default_value);
+        return intent.getIntExtra(extra, defaultValue);
     }
 
     /**
@@ -143,7 +143,7 @@
      * the default Bluetooth UUID is appended.
      * @hide
      */
-    static private UUID getUuidExtra(Intent intent) {
+    private static UUID getUuidExtra(Intent intent) {
         String uuidStr = intent.getStringExtra(EXTRA_UUID);
         if (uuidStr != null && uuidStr.length() == 4) {
             uuidStr = String.format("0000%s-0000-1000-8000-00805f9b34fb", uuidStr);
@@ -155,9 +155,9 @@
      * Log usage information.
      * @hide
      */
-    static private void logUsageInfo() {
+    private static void logUsageInfo() {
         StringBuilder b = new StringBuilder();
-        b.append(  "------------ GATT TEST ACTIONS  ----------------");
+        b.append("------------ GATT TEST ACTIONS  ----------------");
         b.append("\nGATT_TEST_ENABLE");
         b.append("\n  [--ez enable <bool>] Enable or disable,");
         b.append("\n                       defaults to true (enable).\n");
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index 9a99bcd..fd8551a 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.gatt;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -52,15 +54,15 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AbstractionLayer;
 import com.android.bluetooth.btservice.AdapterService;
-import com.android.bluetooth.btservice.BluetoothProto;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.util.NumberUtils;
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.security.Security;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -72,7 +74,6 @@
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 /**
  * Provides Bluetooth Gatt profile, as a service in
  * the Bluetooth application.
@@ -98,28 +99,30 @@
     private static final int ET_LEGACY_MASK = 0x10;
 
     private static final UUID[] HID_UUIDS = {
-        UUID.fromString("00002A4A-0000-1000-8000-00805F9B34FB"),
-        UUID.fromString("00002A4B-0000-1000-8000-00805F9B34FB"),
-        UUID.fromString("00002A4C-0000-1000-8000-00805F9B34FB"),
-        UUID.fromString("00002A4D-0000-1000-8000-00805F9B34FB")
+            UUID.fromString("00002A4A-0000-1000-8000-00805F9B34FB"),
+            UUID.fromString("00002A4B-0000-1000-8000-00805F9B34FB"),
+            UUID.fromString("00002A4C-0000-1000-8000-00805F9B34FB"),
+            UUID.fromString("00002A4D-0000-1000-8000-00805F9B34FB")
     };
 
     private static final UUID[] FIDO_UUIDS = {
-        UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB") // U2F
+            UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB") // U2F
     };
 
     /**
      * Keep the arguments passed in for the PendingIntent.
      */
     class PendingIntentInfo {
-        PendingIntent intent;
-        ScanSettings settings;
-        List<ScanFilter> filters;
-        String callingPackage;
+        public PendingIntent intent;
+        public ScanSettings settings;
+        public List<ScanFilter> filters;
+        public String callingPackage;
 
         @Override
         public boolean equals(Object other) {
-            if (!(other instanceof PendingIntentInfo)) return false;
+            if (!(other instanceof PendingIntentInfo)) {
+                return false;
+            }
             return intent.equals(((PendingIntentInfo) other).intent);
         }
     }
@@ -128,18 +131,21 @@
      * List of our registered scanners.
      */
     class ScannerMap extends ContextMap<IScannerCallback, PendingIntentInfo> {}
+
     ScannerMap mScannerMap = new ScannerMap();
 
     /**
      * List of our registered clients.
      */
     class ClientMap extends ContextMap<IBluetoothGattCallback, Void> {}
+
     ClientMap mClientMap = new ClientMap();
 
     /**
      * List of our registered server apps.
      */
     class ServerMap extends ContextMap<IBluetoothGattServerCallback, Void> {}
+
     ServerMap mServerMap = new ServerMap();
 
     /**
@@ -150,21 +156,23 @@
 
     private int mMaxScanFilters;
 
-    static final int NUM_SCAN_EVENTS_KEPT = 20;
+    private static final int NUM_SCAN_EVENTS_KEPT = 20;
     /**
      * Internal list of scan events to use with the proto
      */
-    ArrayList<BluetoothProto.ScanEvent> mScanEvents =
-        new ArrayList<BluetoothProto.ScanEvent>(NUM_SCAN_EVENTS_KEPT);
+    private final ArrayList<BluetoothMetricsProto.ScanEvent> mScanEvents =
+            new ArrayList<>(NUM_SCAN_EVENTS_KEPT);
 
-    private Map<Integer, List<BluetoothGattService>> gattClientDatabases =
-            new HashMap<Integer, List<BluetoothGattService>>();
+    private final Map<Integer, List<BluetoothGattService>> mGattClientDatabases = new HashMap<>();
 
+    private BluetoothAdapter mAdapter;
     private AdvertiseManager mAdvertiseManager;
     private PeriodicScanManager mPeriodicScanManager;
     private ScanManager mScanManager;
     private AppOpsManager mAppOps;
 
+    private static GattService sGattService;
+
     /**
      * Reliable write queue
      */
@@ -174,17 +182,18 @@
         classInitNative();
     }
 
-    protected String getName() {
-        return TAG;
-    }
-
+    @Override
     protected IProfileServiceBinder initBinder() {
         return new BluetoothGattBinder(this);
     }
 
+    @Override
     protected boolean start() {
-        if (DBG) Log.d(TAG, "start()");
+        if (DBG) {
+            Log.d(TAG, "start()");
+        }
         initializeNative();
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
         mAppOps = getSystemService(AppOpsManager.class);
         mAdvertiseManager = new AdvertiseManager(this, AdapterService.getAdapterService());
         mAdvertiseManager.start();
@@ -195,61 +204,100 @@
         mPeriodicScanManager = new PeriodicScanManager(AdapterService.getAdapterService());
         mPeriodicScanManager.start();
 
+        setGattService(this);
         return true;
     }
 
+    @Override
     protected boolean stop() {
-        if (DBG) Log.d(TAG, "stop()");
+        if (DBG) {
+            Log.d(TAG, "stop()");
+        }
+        setGattService(null);
         mScannerMap.clear();
         mClientMap.clear();
         mServerMap.clear();
         mHandleMap.clear();
         mReliableQueue.clear();
-        if (mAdvertiseManager != null) mAdvertiseManager.cleanup();
-        if (mScanManager != null) mScanManager.cleanup();
-        if (mPeriodicScanManager != null) mPeriodicScanManager.cleanup();
+        if (mAdvertiseManager != null) {
+            mAdvertiseManager.cleanup();
+        }
+        if (mScanManager != null) {
+            mScanManager.cleanup();
+        }
+        if (mPeriodicScanManager != null) {
+            mPeriodicScanManager.cleanup();
+        }
         return true;
     }
 
-    protected boolean cleanup() {
-        if (DBG) Log.d(TAG, "cleanup()");
+    @Override
+    protected void cleanup() {
+        if (DBG) {
+            Log.d(TAG, "cleanup()");
+        }
         cleanupNative();
-        if (mAdvertiseManager != null) mAdvertiseManager.cleanup();
-        if (mScanManager != null) mScanManager.cleanup();
-        if (mPeriodicScanManager != null) mPeriodicScanManager.cleanup();
-        return true;
+        if (mAdvertiseManager != null) {
+            mAdvertiseManager.cleanup();
+        }
+        if (mScanManager != null) {
+            mScanManager.cleanup();
+        }
+        if (mPeriodicScanManager != null) {
+            mPeriodicScanManager.cleanup();
+        }
+    }
+
+
+    /**
+     * Get the current instance of {@link GattService}
+     *
+     * @return current instance of {@link GattService}
+     */
+    @VisibleForTesting
+    public static synchronized GattService getGattService() {
+        if (sGattService == null) {
+            Log.w(TAG, "getGattService(): service is null");
+            return null;
+        }
+        if (!sGattService.isAvailable()) {
+            Log.w(TAG, "getGattService(): service is not available");
+            return null;
+        }
+        return sGattService;
+    }
+
+    private static synchronized void setGattService(GattService instance) {
+        if (DBG) {
+            Log.d(TAG, "setGattService(): set to: " + instance);
+        }
+        sGattService = instance;
     }
 
     boolean permissionCheck(UUID uuid) {
-        if (isRestrictedCharUuid(uuid) && (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)))
-            return false;
-        else
-            return true;
+        return !(isRestrictedCharUuid(uuid) && (0 != checkCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED)));
     }
 
     boolean permissionCheck(int connId, int handle) {
-        List<BluetoothGattService> db = gattClientDatabases.get(connId);
-        if (db == null) return true;
+        List<BluetoothGattService> db = mGattClientDatabases.get(connId);
+        if (db == null) {
+            return true;
+        }
 
         for (BluetoothGattService service : db) {
-            for (BluetoothGattCharacteristic characteristic: service.getCharacteristics()) {
+            for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
                 if (handle == characteristic.getInstanceId()) {
-                    if ((isRestrictedCharUuid(characteristic.getUuid()) ||
-                         isRestrictedSrvcUuid(service.getUuid())) &&
-                        (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)))
-                        return false;
-                    else
-                        return true;
+                    return !((isRestrictedCharUuid(characteristic.getUuid())
+                            || isRestrictedSrvcUuid(service.getUuid()))
+                            && (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
                 }
 
-                for (BluetoothGattDescriptor descriptor: characteristic.getDescriptors()) {
+                for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
                     if (handle == descriptor.getInstanceId()) {
-                        if ((isRestrictedCharUuid(characteristic.getUuid()) ||
-                             isRestrictedSrvcUuid(service.getUuid())) &&
-                            (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)))
-                            return false;
-                        else
-                            return true;
+                        return !((isRestrictedCharUuid(characteristic.getUuid())
+                                || isRestrictedSrvcUuid(service.getUuid())) && (0
+                                != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
                     }
                 }
             }
@@ -274,13 +322,15 @@
     class ScannerDeathRecipient implements IBinder.DeathRecipient {
         int mScannerId;
 
-        public ScannerDeathRecipient(int scannerId) {
+        ScannerDeathRecipient(int scannerId) {
             mScannerId = scannerId;
         }
 
         @Override
         public void binderDied() {
-            if (DBG) Log.d(TAG, "Binder is dead - unregistering scanner (" + mScannerId + ")!");
+            if (DBG) {
+                Log.d(TAG, "Binder is dead - unregistering scanner (" + mScannerId + ")!");
+            }
 
             if (isScanClient(mScannerId)) {
                 ScanClient client = new ScanClient(mScannerId);
@@ -307,12 +357,15 @@
     class ServerDeathRecipient implements IBinder.DeathRecipient {
         int mAppIf;
 
-        public ServerDeathRecipient(int appIf) {
+        ServerDeathRecipient(int appIf) {
             mAppIf = appIf;
         }
 
+        @Override
         public void binderDied() {
-            if (DBG) Log.d(TAG, "Binder is dead - unregistering server (" + mAppIf + ")!");
+            if (DBG) {
+                Log.d(TAG, "Binder is dead - unregistering server (" + mAppIf + ")!");
+            }
             unregisterServer(mAppIf);
         }
     }
@@ -320,12 +373,15 @@
     class ClientDeathRecipient implements IBinder.DeathRecipient {
         int mAppIf;
 
-        public ClientDeathRecipient(int appIf) {
+        ClientDeathRecipient(int appIf) {
             mAppIf = appIf;
         }
 
+        @Override
         public void binderDied() {
-            if (DBG) Log.d(TAG, "Binder is dead - unregistering client (" + mAppIf + ")!");
+            if (DBG) {
+                Log.d(TAG, "Binder is dead - unregistering client (" + mAppIf + ")!");
+            }
             unregisterClient(mAppIf);
         }
     }
@@ -333,52 +389,70 @@
     /**
      * Handlers for incoming service calls
      */
-    private static class BluetoothGattBinder extends IBluetoothGatt.Stub implements IProfileServiceBinder {
+    private static class BluetoothGattBinder extends IBluetoothGatt.Stub
+            implements IProfileServiceBinder {
         private GattService mService;
 
-        public BluetoothGattBinder(GattService svc) {
+        BluetoothGattBinder(GattService svc) {
             mService = svc;
         }
 
-        public boolean cleanup()  {
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
         private GattService getService() {
-            if (mService  != null && mService.isAvailable()) return mService;
+            if (mService != null && mService.isAvailable()) {
+                return mService;
+            }
             Log.e(TAG, "getService() - Service requested, but not available!");
             return null;
         }
 
+        @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
             GattService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>();
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>();
+            }
             return service.getDevicesMatchingConnectionStates(states);
         }
 
+        @Override
         public void registerClient(ParcelUuid uuid, IBluetoothGattCallback callback) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.registerClient(uuid.getUuid(), callback);
         }
 
+        @Override
         public void unregisterClient(int clientIf) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.unregisterClient(clientIf);
         }
 
+        @Override
         public void registerScanner(IScannerCallback callback, WorkSource workSource)
                 throws RemoteException {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.registerScanner(callback, workSource);
         }
 
+        @Override
         public void unregisterScanner(int scannerId) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.unregisterScanner(scannerId);
         }
 
@@ -386,7 +460,9 @@
         public void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
                 List storages, String callingPackage) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.startScan(scannerId, settings, filters, storages, callingPackage);
         }
 
@@ -394,7 +470,9 @@
         public void startScanForIntent(PendingIntent intent, ScanSettings settings,
                 List<ScanFilter> filters, String callingPackage) throws RemoteException {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.registerPiAndStartScan(intent, settings, filters, callingPackage);
         }
 
@@ -402,20 +480,27 @@
         public void stopScanForIntent(PendingIntent intent, String callingPackage)
                 throws RemoteException {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.stopScan(intent, callingPackage);
         }
 
+        @Override
         public void stopScan(int scannerId) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.stopScan(new ScanClient(scannerId));
         }
 
         @Override
         public void flushPendingBatchResults(int scannerId) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.flushPendingBatchResults(scannerId);
         }
 
@@ -423,255 +508,384 @@
         public void clientConnect(int clientIf, String address, boolean isDirect, int transport,
                 boolean opportunistic, int phy) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.clientConnect(clientIf, address, isDirect, transport, opportunistic, phy);
         }
 
         @Override
         public void clientDisconnect(int clientIf, String address) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.clientDisconnect(clientIf, address);
         }
 
         @Override
-        public void clientSetPreferredPhy(
-                int clientIf, String address, int txPhy, int rxPhy, int phyOptions) {
+        public void clientSetPreferredPhy(int clientIf, String address, int txPhy, int rxPhy,
+                int phyOptions) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.clientSetPreferredPhy(clientIf, address, txPhy, rxPhy, phyOptions);
         }
 
         @Override
         public void clientReadPhy(int clientIf, String address) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.clientReadPhy(clientIf, address);
         }
 
+        @Override
         public void refreshDevice(int clientIf, String address) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.refreshDevice(clientIf, address);
         }
 
+        @Override
         public void discoverServices(int clientIf, String address) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.discoverServices(clientIf, address);
         }
 
+        @Override
         public void discoverServiceByUuid(int clientIf, String address, ParcelUuid uuid) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.discoverServiceByUuid(clientIf, address, uuid.getUuid());
         }
 
+        @Override
         public void readCharacteristic(int clientIf, String address, int handle, int authReq) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.readCharacteristic(clientIf, address, handle, authReq);
         }
 
+        @Override
         public void readUsingCharacteristicUuid(int clientIf, String address, ParcelUuid uuid,
                 int startHandle, int endHandle, int authReq) {
             GattService service = getService();
-            if (service == null) return;
-            service.readUsingCharacteristicUuid(
-                    clientIf, address, uuid.getUuid(), startHandle, endHandle, authReq);
+            if (service == null) {
+                return;
+            }
+            service.readUsingCharacteristicUuid(clientIf, address, uuid.getUuid(), startHandle,
+                    endHandle, authReq);
         }
 
-        public void writeCharacteristic(int clientIf, String address, int handle,
-                             int writeType, int authReq, byte[] value) {
+        @Override
+        public void writeCharacteristic(int clientIf, String address, int handle, int writeType,
+                int authReq, byte[] value) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.writeCharacteristic(clientIf, address, handle, writeType, authReq, value);
         }
 
+        @Override
         public void readDescriptor(int clientIf, String address, int handle, int authReq) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.readDescriptor(clientIf, address, handle, authReq);
         }
 
-        public void writeDescriptor(int clientIf, String address, int handle,
-                                    int authReq, byte[] value) {
+        @Override
+        public void writeDescriptor(int clientIf, String address, int handle, int authReq,
+                byte[] value) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.writeDescriptor(clientIf, address, handle, authReq, value);
         }
 
+        @Override
         public void beginReliableWrite(int clientIf, String address) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.beginReliableWrite(clientIf, address);
         }
 
+        @Override
         public void endReliableWrite(int clientIf, String address, boolean execute) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.endReliableWrite(clientIf, address, execute);
         }
 
-        public void registerForNotification(int clientIf, String address, int handle, boolean enable) {
+        @Override
+        public void registerForNotification(int clientIf, String address, int handle,
+                boolean enable) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.registerForNotification(clientIf, address, handle, enable);
         }
 
+        @Override
         public void readRemoteRssi(int clientIf, String address) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.readRemoteRssi(clientIf, address);
         }
 
+        @Override
         public void configureMTU(int clientIf, String address, int mtu) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.configureMTU(clientIf, address, mtu);
         }
 
+        @Override
         public void connectionParameterUpdate(int clientIf, String address,
-                                              int connectionPriority) {
+                int connectionPriority) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.connectionParameterUpdate(clientIf, address, connectionPriority);
         }
 
+        @Override
+        public void leConnectionUpdate(int clientIf, String address,
+                int minConnectionInterval, int maxConnectionInterval,
+                int slaveLatency, int supervisionTimeout,
+                int minConnectionEventLen, int maxConnectionEventLen) {
+            GattService service = getService();
+            if (service == null) {
+                return;
+            }
+            service.leConnectionUpdate(clientIf, address, minConnectionInterval,
+                                       maxConnectionInterval, slaveLatency,
+                                       supervisionTimeout, minConnectionEventLen,
+                                       maxConnectionEventLen);
+        }
+
+        @Override
         public void registerServer(ParcelUuid uuid, IBluetoothGattServerCallback callback) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.registerServer(uuid.getUuid(), callback);
         }
 
+        @Override
         public void unregisterServer(int serverIf) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.unregisterServer(serverIf);
         }
 
+        @Override
         public void serverConnect(int serverIf, String address, boolean isDirect, int transport) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.serverConnect(serverIf, address, isDirect, transport);
         }
 
+        @Override
         public void serverDisconnect(int serverIf, String address) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.serverDisconnect(serverIf, address);
         }
 
-        public void serverSetPreferredPhy(
-                int serverIf, String address, int txPhy, int rxPhy, int phyOptions) {
+        @Override
+        public void serverSetPreferredPhy(int serverIf, String address, int txPhy, int rxPhy,
+                int phyOptions) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.serverSetPreferredPhy(serverIf, address, txPhy, rxPhy, phyOptions);
         }
 
+        @Override
         public void serverReadPhy(int clientIf, String address) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.serverReadPhy(clientIf, address);
         }
 
+        @Override
         public void addService(int serverIf, BluetoothGattService svc) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
 
             service.addService(serverIf, svc);
         }
 
+        @Override
         public void removeService(int serverIf, int handle) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.removeService(serverIf, handle);
         }
 
+        @Override
         public void clearServices(int serverIf) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.clearServices(serverIf);
         }
 
-        public void sendResponse(int serverIf, String address, int requestId,
-                                 int status, int offset, byte[] value) {
+        @Override
+        public void sendResponse(int serverIf, String address, int requestId, int status,
+                int offset, byte[] value) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.sendResponse(serverIf, address, requestId, status, offset, value);
         }
 
-        public void sendNotification(int serverIf, String address, int handle,
-                                              boolean confirm, byte[] value) {
+        @Override
+        public void sendNotification(int serverIf, String address, int handle, boolean confirm,
+                byte[] value) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.sendNotification(serverIf, address, handle, confirm, value);
         }
 
+        @Override
         public void startAdvertisingSet(AdvertisingSetParameters parameters,
                 AdvertiseData advertiseData, AdvertiseData scanResponse,
                 PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData,
                 int duration, int maxExtAdvEvents, IAdvertisingSetCallback callback) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
                     periodicData, duration, maxExtAdvEvents, callback);
         }
 
+        @Override
         public void stopAdvertisingSet(IAdvertisingSetCallback callback) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.stopAdvertisingSet(callback);
         }
 
+        @Override
         public void getOwnAddress(int advertiserId) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.getOwnAddress(advertiserId);
         }
 
-        public void enableAdvertisingSet(
-                int advertiserId, boolean enable, int duration, int maxExtAdvEvents) {
+        @Override
+        public void enableAdvertisingSet(int advertiserId, boolean enable, int duration,
+                int maxExtAdvEvents) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents);
         }
 
+        @Override
         public void setAdvertisingData(int advertiserId, AdvertiseData data) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setAdvertisingData(advertiserId, data);
         }
 
+        @Override
         public void setScanResponseData(int advertiserId, AdvertiseData data) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setScanResponseData(advertiserId, data);
         }
 
-        public void setAdvertisingParameters(
-                int advertiserId, AdvertisingSetParameters parameters) {
+        @Override
+        public void setAdvertisingParameters(int advertiserId,
+                AdvertisingSetParameters parameters) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setAdvertisingParameters(advertiserId, parameters);
         }
 
-        public void setPeriodicAdvertisingParameters(
-                int advertiserId, PeriodicAdvertisingParameters parameters) {
+        @Override
+        public void setPeriodicAdvertisingParameters(int advertiserId,
+                PeriodicAdvertisingParameters parameters) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setPeriodicAdvertisingParameters(advertiserId, parameters);
         }
 
+        @Override
         public void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setPeriodicAdvertisingData(advertiserId, data);
         }
 
+        @Override
         public void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setPeriodicAdvertisingEnable(advertiserId, enable);
         }
 
@@ -679,64 +893,74 @@
         public void registerSync(ScanResult scanResult, int skip, int timeout,
                 IPeriodicAdvertisingCallback callback) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.registerSync(scanResult, skip, timeout, callback);
         }
 
         @Override
         public void unregisterSync(IPeriodicAdvertisingCallback callback) {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.unregisterSync(callback);
         }
 
         @Override
         public void disconnectAll() {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.disconnectAll();
         }
 
         @Override
         public void unregAll() {
             GattService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.unregAll();
         }
 
         @Override
         public int numHwTrackFiltersAvailable() {
             GattService service = getService();
-            if (service == null) return 0;
+            if (service == null) {
+                return 0;
+            }
             return service.numHwTrackFiltersAvailable();
         }
-    };
+    }
+
+    ;
 
     /**************************************************************************
      * Callback functions - CLIENT
      *************************************************************************/
 
-    void onScanResult(int event_type, int address_type, String address, int primary_phy,
-            int secondary_phy, int advertising_sid, int tx_power, int rssi, int periodic_adv_int,
-            byte[] adv_data) {
+    void onScanResult(int eventType, int addressType, String address, int primaryPhy,
+            int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvInt,
+            byte[] advData) {
         if (VDBG) {
-            Log.d(TAG, "onScanResult() - event_type=0x" + Integer.toHexString(event_type)
-                            + ", address_type=" + address_type + ", address=" + address
-                            + ", primary_phy=" + primary_phy + ", secondary_phy=" + secondary_phy
-                            + ", advertising_sid=0x" + Integer.toHexString(advertising_sid)
-                            + ", tx_power=" + tx_power + ", rssi=" + rssi + ", periodic_adv_int=0x"
-                            + Integer.toHexString(periodic_adv_int));
+            Log.d(TAG, "onScanResult() - eventType=0x" + Integer.toHexString(eventType)
+                    + ", addressType=" + addressType + ", address=" + address + ", primaryPhy="
+                    + primaryPhy + ", secondaryPhy=" + secondaryPhy + ", advertisingSid=0x"
+                    + Integer.toHexString(advertisingSid) + ", txPower=" + txPower + ", rssi="
+                    + rssi + ", periodicAdvInt=0x" + Integer.toHexString(periodicAdvInt));
         }
-        List<UUID> remoteUuids = parseUuids(adv_data);
-        addScanResult();
+        List<UUID> remoteUuids = parseUuids(advData);
 
-        byte[] legacy_adv_data = Arrays.copyOfRange(adv_data, 0, 62);
+        byte[] legacyAdvData = Arrays.copyOfRange(advData, 0, 62);
 
         for (ScanClient client : mScanManager.getRegularScanQueue()) {
             if (client.uuids.length > 0) {
                 int matches = 0;
                 for (UUID search : client.uuids) {
-                    for (UUID remote: remoteUuids) {
+                    for (UUID remote : remoteUuids) {
                         if (remote.equals(search)) {
                             ++matches;
                             break; // Only count 1st match in case of duplicates
@@ -744,7 +968,9 @@
                     }
                 }
 
-                if (matches < client.uuids.length) continue;
+                if (matches < client.uuids.length) {
+                    continue;
+                }
             }
 
             ScannerMap.App app = mScannerMap.getById(client.scannerId);
@@ -755,24 +981,25 @@
             BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
 
             ScanSettings settings = client.settings;
-            byte[] scan_record_data;
+            byte[] scanRecordData;
             // This is for compability with applications that assume fixed size scan data.
             if (settings.getLegacy()) {
-                if ((event_type & ET_LEGACY_MASK) == 0) {
+                if ((eventType & ET_LEGACY_MASK) == 0) {
                     // If this is legacy scan, but nonlegacy result - skip.
                     continue;
                 } else {
                     // Some apps are used to fixed-size advertise data.
-                    scan_record_data = legacy_adv_data;
+                    scanRecordData = legacyAdvData;
                 }
             } else {
-                scan_record_data = adv_data;
+                scanRecordData = advData;
             }
 
-            ScanResult result = new ScanResult(device, event_type, primary_phy, secondary_phy,
-                    advertising_sid, tx_power, rssi, periodic_adv_int,
-                    ScanRecord.parseFromBytes(scan_record_data),
-                    SystemClock.elapsedRealtimeNanos());
+            ScanResult result =
+                    new ScanResult(device, eventType, primaryPhy, secondaryPhy, advertisingSid,
+                            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
             if (!hasScanResultPermission(client) || !matchesFilters(client, result)) {
@@ -817,10 +1044,9 @@
     private void sendResultsByPendingIntent(PendingIntentInfo pii, ArrayList<ScanResult> results,
             int callbackType) throws PendingIntent.CanceledException {
         Intent extrasIntent = new Intent();
-        extrasIntent.putParcelableArrayListExtra(
-                BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT, results);
-        extrasIntent.putExtra(
-                BluetoothLeScanner.EXTRA_CALLBACK_TYPE, callbackType);
+        extrasIntent.putParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT,
+                results);
+        extrasIntent.putExtra(BluetoothLeScanner.EXTRA_CALLBACK_TYPE, callbackType);
         pii.intent.send(this, 0, extrasIntent);
     }
 
@@ -834,8 +1060,10 @@
     void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb)
             throws RemoteException {
         UUID uuid = new UUID(uuidMsb, uuidLsb);
-        if (DBG) Log.d(TAG, "onScannerRegistered() - UUID=" + uuid
-                + ", scannerId=" + scannerId + ", status=" + status);
+        if (DBG) {
+            Log.d(TAG, "onScannerRegistered() - UUID=" + uuid + ", scannerId=" + scannerId
+                    + ", status=" + status);
+        }
 
         // First check the callback map
         ScannerMap.App cbApp = mScannerMap.getByUuid(uuid);
@@ -862,13 +1090,13 @@
     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));
+        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));
     }
 
     // Check if a scan record matches a specific filters.
@@ -887,7 +1115,9 @@
     void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb)
             throws RemoteException {
         UUID uuid = new UUID(uuidMsb, uuidLsb);
-        if (DBG) Log.d(TAG, "onClientRegistered() - UUID=" + uuid + ", clientIf=" + clientIf);
+        if (DBG) {
+            Log.d(TAG, "onClientRegistered() - UUID=" + uuid + ", clientIf=" + clientIf);
+        }
         ClientMap.App app = mClientMap.getByUuid(uuid);
         if (app != null) {
             if (status == 0) {
@@ -900,23 +1130,29 @@
         }
     }
 
-    void onConnected(int clientIf, int connId, int status, String address)
-            throws RemoteException  {
-        if (DBG) Log.d(TAG, "onConnected() - clientIf=" + clientIf
-            + ", connId=" + connId + ", address=" + address);
+    void onConnected(int clientIf, int connId, int status, String address) throws RemoteException {
+        if (DBG) {
+            Log.d(TAG, "onConnected() - clientIf=" + clientIf + ", connId=" + connId + ", address="
+                    + address);
+        }
 
-        if (status == 0) mClientMap.addConnection(clientIf, connId, address);
+        if (status == 0) {
+            mClientMap.addConnection(clientIf, connId, address);
+        }
         ClientMap.App app = mClientMap.getById(clientIf);
         if (app != null) {
             app.callback.onClientConnectionState(status, clientIf,
-                                (status==BluetoothGatt.GATT_SUCCESS), address);
+                    (status == BluetoothGatt.GATT_SUCCESS), address);
         }
     }
 
     void onDisconnected(int clientIf, int connId, int status, String address)
             throws RemoteException {
-        if (DBG) Log.d(TAG, "onDisconnected() - clientIf=" + clientIf
-            + ", connId=" + connId + ", address=" + address);
+        if (DBG) {
+            Log.d(TAG,
+                    "onDisconnected() - clientIf=" + clientIf + ", connId=" + connId + ", address="
+                            + address);
+        }
 
         mClientMap.removeConnection(clientIf, connId);
         ClientMap.App app = mClientMap.getById(clientIf);
@@ -926,22 +1162,30 @@
     }
 
     void onClientPhyUpdate(int connId, int txPhy, int rxPhy, int status) throws RemoteException {
-        if (DBG) Log.d(TAG, "onClientPhyUpdate() - connId=" + connId + ", status=" + status);
+        if (DBG) {
+            Log.d(TAG, "onClientPhyUpdate() - connId=" + connId + ", status=" + status);
+        }
 
         String address = mClientMap.addressByConnId(connId);
-        if (address == null) return;
+        if (address == null) {
+            return;
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onPhyUpdate(address, txPhy, rxPhy, status);
     }
 
     void onClientPhyRead(int clientIf, String address, int txPhy, int rxPhy, int status)
             throws RemoteException {
-        if (DBG)
-            Log.d(TAG, "onClientPhyRead() - address=" + address + ", status=" + status
-                            + ", clientIf=" + clientIf);
+        if (DBG) {
+            Log.d(TAG,
+                    "onClientPhyRead() - address=" + address + ", status=" + status + ", clientIf="
+                            + clientIf);
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
@@ -950,39 +1194,55 @@
         }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onPhyRead(address, txPhy, rxPhy, status);
     }
 
     void onClientConnUpdate(int connId, int interval, int latency, int timeout, int status)
             throws RemoteException {
-        if (DBG) Log.d(TAG, "onClientConnUpdate() - connId=" + connId + ", status=" + status);
+        if (DBG) {
+            Log.d(TAG, "onClientConnUpdate() - connId=" + connId + ", status=" + status);
+        }
 
         String address = mClientMap.addressByConnId(connId);
-        if (address == null) return;
+        if (address == null) {
+            return;
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onConnectionUpdated(address, interval, latency, timeout, status);
     }
 
     void onServerPhyUpdate(int connId, int txPhy, int rxPhy, int status) throws RemoteException {
-        if (DBG) Log.d(TAG, "onServerPhyUpdate() - connId=" + connId + ", status=" + status);
+        if (DBG) {
+            Log.d(TAG, "onServerPhyUpdate() - connId=" + connId + ", status=" + status);
+        }
 
         String address = mServerMap.addressByConnId(connId);
-        if (address == null) return;
+        if (address == null) {
+            return;
+        }
 
         ServerMap.App app = mServerMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onPhyUpdate(address, txPhy, rxPhy, status);
     }
 
     void onServerPhyRead(int serverIf, String address, int txPhy, int rxPhy, int status)
             throws RemoteException {
-        if (DBG) Log.d(TAG, "onServerPhyRead() - address=" + address + ", status=" + status);
+        if (DBG) {
+            Log.d(TAG, "onServerPhyRead() - address=" + address + ", status=" + status);
+        }
 
         Integer connId = mServerMap.connIdByAddress(serverIf, address);
         if (connId == null) {
@@ -991,31 +1251,42 @@
         }
 
         ServerMap.App app = mServerMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onPhyRead(address, txPhy, rxPhy, status);
     }
 
     void onServerConnUpdate(int connId, int interval, int latency, int timeout, int status)
             throws RemoteException {
-        if (DBG) Log.d(TAG, "onServerConnUpdate() - connId=" + connId + ", status=" + status);
+        if (DBG) {
+            Log.d(TAG, "onServerConnUpdate() - connId=" + connId + ", status=" + status);
+        }
 
         String address = mServerMap.addressByConnId(connId);
-        if (address == null) return;
+        if (address == null) {
+            return;
+        }
 
         ServerMap.App app = mServerMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onConnectionUpdated(address, interval, latency, timeout, status);
     }
 
     void onSearchCompleted(int connId, int status) throws RemoteException {
-        if (DBG) Log.d(TAG, "onSearchCompleted() - connId=" + connId+ ", status=" + status);
+        if (DBG) {
+            Log.d(TAG, "onSearchCompleted() - connId=" + connId + ", status=" + status);
+        }
         // Gatt DB is ready!
 
         // This callback was called from the jni_workqueue thread. If we make request to the stack
         // on the same thread, it might cause deadlock. Schedule request on a new thread instead.
         Thread t = new Thread(new Runnable() {
+            @Override
             public void run() {
                 gattClientGetGattDbNative(connId);
             }
@@ -1023,14 +1294,16 @@
         t.start();
     }
 
-    GattDbElement GetSampleGattDbElement() {
+    GattDbElement getSampleGattDbElement() {
         return new GattDbElement();
     }
 
     void onGetGattDb(int connId, ArrayList<GattDbElement> db) throws RemoteException {
         String address = mClientMap.addressByConnId(connId);
 
-        if (DBG) Log.d(TAG, "onGetGattDb() - address=" + address);
+        if (DBG) {
+            Log.d(TAG, "onGetGattDb() - address=" + address);
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
         if (app == null || app.callback == null) {
@@ -1038,64 +1311,77 @@
             return;
         }
 
-        List<BluetoothGattService> db_out = new ArrayList<BluetoothGattService>();
+        List<BluetoothGattService> dbOut = new ArrayList<BluetoothGattService>();
 
         BluetoothGattService currSrvc = null;
         BluetoothGattCharacteristic currChar = null;
 
-        for (GattDbElement el: db) {
-            switch (el.type)
-            {
+        for (GattDbElement el : db) {
+            switch (el.type) {
                 case GattDbElement.TYPE_PRIMARY_SERVICE:
                 case GattDbElement.TYPE_SECONDARY_SERVICE:
-                    if (DBG) Log.d(TAG, "got service with UUID=" + el.uuid);
+                    if (DBG) {
+                        Log.d(TAG, "got service with UUID=" + el.uuid + " id: " + el.id);
+                    }
 
                     currSrvc = new BluetoothGattService(el.uuid, el.id, el.type);
-                    db_out.add(currSrvc);
+                    dbOut.add(currSrvc);
                     break;
 
                 case GattDbElement.TYPE_CHARACTERISTIC:
-                    if (DBG) Log.d(TAG, "got characteristic with UUID=" + el.uuid);
+                    if (DBG) {
+                        Log.d(TAG, "got characteristic with UUID=" + el.uuid + " id: " + el.id);
+                    }
 
                     currChar = new BluetoothGattCharacteristic(el.uuid, el.id, el.properties, 0);
                     currSrvc.addCharacteristic(currChar);
                     break;
 
                 case GattDbElement.TYPE_DESCRIPTOR:
-                    if (DBG) Log.d(TAG, "got descriptor with UUID=" + el.uuid);
+                    if (DBG) {
+                        Log.d(TAG, "got descriptor with UUID=" + el.uuid + " id: " + el.id);
+                    }
 
                     currChar.addDescriptor(new BluetoothGattDescriptor(el.uuid, el.id, 0));
                     break;
 
                 case GattDbElement.TYPE_INCLUDED_SERVICE:
-                    if (DBG) Log.d(TAG, "got included service with UUID=" + el.uuid);
+                    if (DBG) {
+                        Log.d(TAG, "got included service with UUID=" + el.uuid + " id: " + el.id
+                                + " startHandle: " + el.startHandle);
+                    }
 
-                    currSrvc.addIncludedService(new BluetoothGattService(el.uuid, el.id, el.type));
+                    currSrvc.addIncludedService(
+                            new BluetoothGattService(el.uuid, el.startHandle, el.type));
                     break;
 
                 default:
-                    Log.e(TAG, "got unknown element with type=" + el.type + " and UUID=" + el.uuid);
+                    Log.e(TAG, "got unknown element with type=" + el.type + " and UUID=" + el.uuid
+                            + " id: " + el.id);
             }
         }
 
         // Search is complete when there was error, or nothing more to process
-        gattClientDatabases.put(connId, db_out);
-        app.callback.onSearchComplete(address, db_out, 0 /* status */);
+        mGattClientDatabases.put(connId, dbOut);
+        app.callback.onSearchComplete(address, dbOut, 0 /* status */);
     }
 
     void onRegisterForNotifications(int connId, int status, int registered, int handle) {
         String address = mClientMap.addressByConnId(connId);
 
-        if (DBG) Log.d(TAG, "onRegisterForNotifications() - address=" + address
-            + ", status=" + status + ", registered=" + registered
-            + ", handle=" + handle);
+        if (DBG) {
+            Log.d(TAG, "onRegisterForNotifications() - address=" + address + ", status=" + status
+                    + ", registered=" + registered + ", handle=" + handle);
+        }
     }
 
-    void onNotify(int connId, String address, int handle,
-            boolean isNotify, byte[] data) throws RemoteException {
+    void onNotify(int connId, String address, int handle, boolean isNotify, byte[] data)
+            throws RemoteException {
 
-        if (VDBG) Log.d(TAG, "onNotify() - address=" + address
-            + ", handle=" + handle + ", length=" + data.length);
+        if (VDBG) {
+            Log.d(TAG, "onNotify() - address=" + address + ", handle=" + handle + ", length="
+                    + data.length);
+        }
 
         if (!permissionCheck(connId, handle)) {
             Log.w(TAG, "onNotify() - permission check failed!");
@@ -1108,11 +1394,14 @@
         }
     }
 
-    void onReadCharacteristic(int connId, int status, int handle, byte[] data) throws RemoteException {
+    void onReadCharacteristic(int connId, int status, int handle, byte[] data)
+            throws RemoteException {
         String address = mClientMap.addressByConnId(connId);
 
-        if (VDBG) Log.d(TAG, "onReadCharacteristic() - address=" + address
-            + ", status=" + status + ", length=" + data.length);
+        if (VDBG) {
+            Log.d(TAG, "onReadCharacteristic() - address=" + address + ", status=" + status
+                    + ", length=" + data.length);
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
         if (app != null) {
@@ -1120,15 +1409,17 @@
         }
     }
 
-    void onWriteCharacteristic(int connId, int status, int handle)
-            throws RemoteException {
+    void onWriteCharacteristic(int connId, int status, int handle) throws RemoteException {
         String address = mClientMap.addressByConnId(connId);
 
-        if (VDBG) Log.d(TAG, "onWriteCharacteristic() - address=" + address
-            + ", status=" + status);
+        if (VDBG) {
+            Log.d(TAG, "onWriteCharacteristic() - address=" + address + ", status=" + status);
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         if (!app.isCongested) {
             app.callback.onCharacteristicWrite(address, status, handle);
@@ -1143,8 +1434,9 @@
 
     void onExecuteCompleted(int connId, int status) throws RemoteException {
         String address = mClientMap.addressByConnId(connId);
-        if (VDBG) Log.d(TAG, "onExecuteCompleted() - address=" + address
-            + ", status=" + status);
+        if (VDBG) {
+            Log.d(TAG, "onExecuteCompleted() - address=" + address + ", status=" + status);
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
         if (app != null) {
@@ -1155,8 +1447,11 @@
     void onReadDescriptor(int connId, int status, int handle, byte[] data) throws RemoteException {
         String address = mClientMap.addressByConnId(connId);
 
-        if (VDBG) Log.d(TAG, "onReadDescriptor() - address=" + address
-            + ", status=" + status + ", length=" + data.length);
+        if (VDBG) {
+            Log.d(TAG,
+                    "onReadDescriptor() - address=" + address + ", status=" + status + ", length="
+                            + data.length);
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
         if (app != null) {
@@ -1167,8 +1462,9 @@
     void onWriteDescriptor(int connId, int status, int handle) throws RemoteException {
         String address = mClientMap.addressByConnId(connId);
 
-        if (VDBG) Log.d(TAG, "onWriteDescriptor() - address=" + address
-            + ", status=" + status);
+        if (VDBG) {
+            Log.d(TAG, "onWriteDescriptor() - address=" + address + ", status=" + status);
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
         if (app != null) {
@@ -1176,10 +1472,13 @@
         }
     }
 
-    void onReadRemoteRssi(int clientIf, String address,
-                    int rssi, int status) throws RemoteException{
-        if (DBG) Log.d(TAG, "onReadRemoteRssi() - clientIf=" + clientIf + " address=" +
-                     address + ", rssi=" + rssi + ", status=" + status);
+    void onReadRemoteRssi(int clientIf, String address, int rssi, int status)
+            throws RemoteException {
+        if (DBG) {
+            Log.d(TAG,
+                    "onReadRemoteRssi() - clientIf=" + clientIf + " address=" + address + ", rssi="
+                            + rssi + ", status=" + status);
+        }
 
         ClientMap.App app = mClientMap.getById(clientIf);
         if (app != null) {
@@ -1197,9 +1496,9 @@
 
     void onScanFilterParamsConfigured(int action, int status, int clientIf, int availableSpace) {
         if (DBG) {
-            Log.d(TAG, "onScanFilterParamsConfigured() - clientIf=" + clientIf
-                    + ", status=" + status + ", action=" + action
-                    + ", availableSpace=" + availableSpace);
+            Log.d(TAG,
+                    "onScanFilterParamsConfigured() - clientIf=" + clientIf + ", status=" + status
+                            + ", action=" + action + ", availableSpace=" + availableSpace);
         }
         mScanManager.callbackDone(clientIf, status);
     }
@@ -1208,8 +1507,8 @@
             int availableSpace) {
         if (DBG) {
             Log.d(TAG, "onScanFilterConfig() - clientIf=" + clientIf + ", action = " + action
-                    + " status = " + status + ", filterType=" + filterType
-                    + ", availableSpace=" + availableSpace);
+                    + " status = " + status + ", filterType=" + filterType + ", availableSpace="
+                    + availableSpace);
         }
 
         mScanManager.callbackDone(clientIf, status);
@@ -1226,8 +1525,8 @@
     // TODO: split into two different callbacks : onBatchScanStarted and onBatchScanStopped.
     void onBatchScanStartStopped(int startStopAction, int status, int clientIf) {
         if (DBG) {
-            Log.d(TAG, "onBatchScanStartStopped() - clientIf=" + clientIf
-                    + ", status=" + status + ", startStopAction=" + startStopAction);
+            Log.d(TAG, "onBatchScanStartStopped() - clientIf=" + clientIf + ", status=" + status
+                    + ", startStopAction=" + startStopAction);
         }
         mScanManager.callbackDone(clientIf, status);
     }
@@ -1243,7 +1542,9 @@
         if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) {
             // We only support single client for truncated mode.
             ScannerMap.App app = mScannerMap.getById(scannerId);
-            if (app == null) return;
+            if (app == null) {
+                return;
+            }
             if (app.callback != null) {
                 app.callback.onBatchScanResults(new ArrayList<ScanResult>(results));
             } else {
@@ -1262,8 +1563,8 @@
         }
     }
 
-    private void sendBatchScanResults(
-            ScannerMap.App app, ScanClient client, ArrayList<ScanResult> results) {
+    private void sendBatchScanResults(ScannerMap.App app, ScanClient client,
+            ArrayList<ScanResult> results) {
         try {
             if (app.callback != null) {
                 app.callback.onBatchScanResults(results);
@@ -1279,10 +1580,12 @@
     }
 
     // Check and deliver scan results for different scan clients.
-    private void deliverBatchScan(ScanClient client, Set<ScanResult> allResults) throws
-            RemoteException {
+    private void deliverBatchScan(ScanClient client, Set<ScanResult> allResults)
+            throws RemoteException {
         ScannerMap.App app = mScannerMap.getById(client.scannerId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
         if (client.filters == null || client.filters.isEmpty()) {
             sendBatchScanResults(app, client, new ArrayList<ScanResult>(allResults));
             // TODO: Question to reviewer: Shouldn't there be a return here?
@@ -1302,7 +1605,9 @@
         if (numRecords == 0) {
             return Collections.emptySet();
         }
-        if (DBG) Log.d(TAG, "current time is " + SystemClock.elapsedRealtimeNanos());
+        if (DBG) {
+            Log.d(TAG, "current time is " + SystemClock.elapsedRealtimeNanos());
+        }
         if (reportType == ScanManager.SCAN_RESULT_TYPE_TRUNCATED) {
             return parseTruncatedResults(numRecords, batchRecord);
         } else {
@@ -1311,19 +1616,21 @@
     }
 
     private Set<ScanResult> parseTruncatedResults(int numRecords, byte[] batchRecord) {
-        if (DBG) Log.d(TAG, "batch record " + Arrays.toString(batchRecord));
+        if (DBG) {
+            Log.d(TAG, "batch record " + Arrays.toString(batchRecord));
+        }
         Set<ScanResult> results = new HashSet<ScanResult>(numRecords);
         long now = SystemClock.elapsedRealtimeNanos();
         for (int i = 0; i < numRecords; ++i) {
-            byte[] record = extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE,
-                    TRUNCATED_RESULT_SIZE);
+            byte[] record =
+                    extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE, TRUNCATED_RESULT_SIZE);
             byte[] address = extractBytes(record, 0, 6);
             reverse(address);
             BluetoothDevice device = mAdapter.getRemoteDevice(address);
             int rssi = record[8];
             long timestampNanos = now - parseTimestampNanos(extractBytes(record, 9, 2));
-            results.add(new ScanResult(device, ScanRecord.parseFromBytes(new byte[0]),
-                    rssi, timestampNanos));
+            results.add(new ScanResult(device, ScanRecord.parseFromBytes(new byte[0]), rssi,
+                    timestampNanos));
         }
         return results;
     }
@@ -1336,7 +1643,9 @@
     }
 
     private Set<ScanResult> parseFullResults(int numRecords, byte[] batchRecord) {
-        if (DBG) Log.d(TAG, "Batch record : " + Arrays.toString(batchRecord));
+        if (DBG) {
+            Log.d(TAG, "Batch record : " + Arrays.toString(batchRecord));
+        }
         Set<ScanResult> results = new HashSet<ScanResult>(numRecords);
         int position = 0;
         long now = SystemClock.elapsedRealtimeNanos();
@@ -1363,11 +1672,13 @@
             position += scanResponsePacketLen;
             byte[] scanRecord = new byte[advertisePacketLen + scanResponsePacketLen];
             System.arraycopy(advertiseBytes, 0, scanRecord, 0, advertisePacketLen);
-            System.arraycopy(scanResponseBytes, 0, scanRecord,
-                    advertisePacketLen, scanResponsePacketLen);
-            if (DBG) Log.d(TAG, "ScanRecord : " + Arrays.toString(scanRecord));
-            results.add(new ScanResult(device, ScanRecord.parseFromBytes(scanRecord),
-                    rssi, timestampNanos));
+            System.arraycopy(scanResponseBytes, 0, scanRecord, advertisePacketLen,
+                    scanResponsePacketLen);
+            if (DBG) {
+                Log.d(TAG, "ScanRecord : " + Arrays.toString(scanRecord));
+            }
+            results.add(new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), rssi,
+                    timestampNanos));
         }
         return results;
     }
@@ -1396,21 +1707,22 @@
         flushPendingBatchResults(clientIf);
     }
 
-    AdvtFilterOnFoundOnLostInfo CreateonTrackAdvFoundLostObject(int client_if, int adv_pkt_len,
-                    byte[] adv_pkt, int scan_rsp_len, byte[] scan_rsp, int filt_index, int adv_state,
-                    int adv_info_present, String address, int addr_type, int tx_power, int rssi_value,
-                    int time_stamp) {
+    AdvtFilterOnFoundOnLostInfo createOnTrackAdvFoundLostObject(int clientIf, int advPktLen,
+            byte[] advPkt, int scanRspLen, byte[] scanRsp, int filtIndex, int advState,
+            int advInfoPresent, String address, int addrType, int txPower, int rssiValue,
+            int timeStamp) {
 
-        return new AdvtFilterOnFoundOnLostInfo(client_if, adv_pkt_len, adv_pkt,
-                    scan_rsp_len, scan_rsp, filt_index, adv_state,
-                    adv_info_present, address, addr_type, tx_power,
-                    rssi_value, time_stamp);
+        return new AdvtFilterOnFoundOnLostInfo(clientIf, advPktLen, advPkt, scanRspLen, scanRsp,
+                filtIndex, advState, advInfoPresent, address, addrType, txPower, rssiValue,
+                timeStamp);
     }
 
     void onTrackAdvFoundLost(AdvtFilterOnFoundOnLostInfo trackingInfo) throws RemoteException {
-        if (DBG) Log.d(TAG, "onTrackAdvFoundLost() - scannerId= " + trackingInfo.getClientIf()
-                    + " address = " + trackingInfo.getAddress()
-                    + " adv_state = " + trackingInfo.getAdvState());
+        if (DBG) {
+            Log.d(TAG, "onTrackAdvFoundLost() - scannerId= " + trackingInfo.getClientIf()
+                    + " address = " + trackingInfo.getAddress() + " adv_state = "
+                    + trackingInfo.getAdvState());
+        }
 
         ScannerMap.App app = mScannerMap.getById(trackingInfo.getClientIf());
         if (app == null || (app.callback == null && app.info == null)) {
@@ -1418,28 +1730,28 @@
             return;
         }
 
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter()
-                        .getRemoteDevice(trackingInfo.getAddress());
+        BluetoothDevice device =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(trackingInfo.getAddress());
         int advertiserState = trackingInfo.getAdvState();
-        ScanResult result = new ScanResult(device,
-                        ScanRecord.parseFromBytes(trackingInfo.getResult()),
+        ScanResult result =
+                new ScanResult(device, ScanRecord.parseFromBytes(trackingInfo.getResult()),
                         trackingInfo.getRSSIValue(), SystemClock.elapsedRealtimeNanos());
 
         for (ScanClient client : mScanManager.getRegularScanQueue()) {
             if (client.scannerId == trackingInfo.getClientIf()) {
                 ScanSettings settings = client.settings;
-                if ((advertiserState == ADVT_STATE_ONFOUND)
-                        && ((settings.getCallbackType()
-                                & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0)) {
+                if ((advertiserState == ADVT_STATE_ONFOUND) && (
+                        (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
+                                != 0)) {
                     if (app.callback != null) {
                         app.callback.onFoundOrLost(true, result);
                     } else {
                         sendResultByPendingIntent(app.info, result,
                                 ScanSettings.CALLBACK_TYPE_FIRST_MATCH, client);
                     }
-                } else if ((advertiserState == ADVT_STATE_ONLOST)
-                                && ((settings.getCallbackType()
-                                        & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0)) {
+                } else if ((advertiserState == ADVT_STATE_ONLOST) && (
+                        (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST)
+                                != 0)) {
                     if (app.callback != null) {
                         app.callback.onFoundOrLost(false, result);
                     } else {
@@ -1449,8 +1761,8 @@
                 } else {
                     if (DBG) {
                         Log.d(TAG, "Not reporting onlost/onfound : " + advertiserState
-                                        + " scannerId = " + client.scannerId + " callbackType "
-                                        + settings.getCallbackType());
+                                + " scannerId = " + client.scannerId + " callbackType "
+                                + settings.getCallbackType());
                     }
                 }
             }
@@ -1463,7 +1775,9 @@
             Log.e(TAG, "Advertise app or callback is null");
             return;
         }
-        if (DBG) Log.d(TAG, "onScanParamSetupCompleted : " + status);
+        if (DBG) {
+            Log.d(TAG, "onScanParamSetupCompleted : " + status);
+        }
     }
 
     // callback from ScanManager for dispatch of errors apps.
@@ -1487,8 +1801,10 @@
     void onConfigureMTU(int connId, int status, int mtu) throws RemoteException {
         String address = mClientMap.addressByConnId(connId);
 
-        if (DBG) Log.d(TAG, "onConfigureMTU() address=" + address + ", status="
-            + status + ", mtu=" + mtu);
+        if (DBG) {
+            Log.d(TAG,
+                    "onConfigureMTU() address=" + address + ", status=" + status + ", mtu=" + mtu);
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
         if (app != null) {
@@ -1497,17 +1813,21 @@
     }
 
     void onClientCongestion(int connId, boolean congested) throws RemoteException {
-        if (VDBG) Log.d(TAG, "onClientCongestion() - connId=" + connId + ", congested=" + congested);
+        if (VDBG) {
+            Log.d(TAG, "onClientCongestion() - connId=" + connId + ", congested=" + congested);
+        }
 
         ClientMap.App app = mClientMap.getByConnId(connId);
 
         if (app != null) {
             app.isCongested = congested;
-            while(!app.isCongested) {
+            while (!app.isCongested) {
                 CallbackInfo callbackInfo = app.popQueuedCallback();
-                if (callbackInfo == null)  return;
-                app.callback.onCharacteristicWrite(callbackInfo.address,
-                        callbackInfo.status, callbackInfo.handle);
+                if (callbackInfo == null) {
+                    return;
+                }
+                app.callback.onCharacteristicWrite(callbackInfo.address, callbackInfo.status,
+                        callbackInfo.handle);
             }
         }
     }
@@ -1519,16 +1839,13 @@
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        final int DEVICE_TYPE_BREDR = 0x1;
-
-        Map<BluetoothDevice, Integer> deviceStates = new HashMap<BluetoothDevice,
-                                                                 Integer>();
+        Map<BluetoothDevice, Integer> deviceStates = new HashMap<BluetoothDevice, Integer>();
 
         // Add paired LE devices
 
         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
         for (BluetoothDevice device : bondedDevices) {
-            if (getDeviceType(device) != DEVICE_TYPE_BREDR) {
+            if (getDeviceType(device) != AbstractionLayer.BT_DEVICE_TYPE_BREDR) {
                 deviceStates.put(device, BluetoothProfile.STATE_DISCONNECTED);
             }
         }
@@ -1539,7 +1856,7 @@
         connectedDevices.addAll(mClientMap.getConnectedDevices());
         connectedDevices.addAll(mServerMap.getConnectedDevices());
 
-        for (String address : connectedDevices ) {
+        for (String address : connectedDevices) {
             BluetoothDevice device = mAdapter.getRemoteDevice(address);
             if (device != null) {
                 deviceStates.put(device, BluetoothProfile.STATE_CONNECTED);
@@ -1551,7 +1868,7 @@
         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
 
         for (Map.Entry<BluetoothDevice, Integer> entry : deviceStates.entrySet()) {
-            for(int state : states) {
+            for (int state : states) {
                 if (entry.getValue() == state) {
                     deviceList.add(entry.getKey());
                 }
@@ -1565,13 +1882,14 @@
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         UUID uuid = UUID.randomUUID();
-        if (DBG) Log.d(TAG, "registerScanner() - UUID=" + uuid);
+        if (DBG) {
+            Log.d(TAG, "registerScanner() - UUID=" + uuid);
+        }
 
         if (workSource != null) {
             enforceImpersonatationPermission();
         }
 
-        mScannerMap.add(uuid, workSource, callback, null, this);
         AppScanStats app = mScannerMap.getAppScanStatsByUid(Binder.getCallingUid());
         if (app != null && app.isScanningTooFrequently()
                 && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) != PERMISSION_GRANTED) {
@@ -1580,29 +1898,34 @@
             return;
         }
 
+        mScannerMap.add(uuid, workSource, callback, null, this);
         mScanManager.registerScanner(uuid);
     }
 
     void unregisterScanner(int scannerId) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "unregisterScanner() - scannerId=" + scannerId);
+        if (DBG) {
+            Log.d(TAG, "unregisterScanner() - scannerId=" + scannerId);
+        }
         mScannerMap.remove(scannerId);
         mScanManager.unregisterScanner(scannerId);
     }
 
     void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
             List<List<ResultStorageDescriptor>> storages, String callingPackage) {
-        if (DBG) Log.d(TAG, "start scan with filters");
+        if (DBG) {
+            Log.d(TAG, "start scan with filters");
+        }
         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.hasLocationPermission =
+                Utils.checkCallerHasLocationPermission(this, mAppOps, callingPackage);
+        scanClient.hasPeersMacAddressPermission =
+                Utils.checkCallerHasPeersMacAddressPermission(this);
         scanClient.legacyForegroundApp = Utils.isLegacyForegroundApp(this, callingPackage);
 
         AppScanStats app = mScannerMap.getAppScanStatsById(scannerId);
@@ -1617,14 +1940,18 @@
 
     void registerPiAndStartScan(PendingIntent pendingIntent, ScanSettings settings,
             List<ScanFilter> filters, String callingPackage) {
-        if (DBG) Log.d(TAG, "start scan with filters, for PendingIntent");
+        if (DBG) {
+            Log.d(TAG, "start scan with filters, for PendingIntent");
+        }
         enforceAdminPermission();
         if (needsPrivilegedPermissionForScan(settings)) {
             enforcePrivilegedPermission();
         }
 
         UUID uuid = UUID.randomUUID();
-        if (DBG) Log.d(TAG, "startScan(PI) - UUID=" + uuid);
+        if (DBG) {
+            Log.d(TAG, "startScan(PI) - UUID=" + uuid);
+        }
         PendingIntentInfo piInfo = new PendingIntentInfo();
         piInfo.intent = pendingIntent;
         piInfo.settings = settings;
@@ -1666,19 +1993,25 @@
     }
 
     void flushPendingBatchResults(int scannerId) {
-        if (DBG) Log.d(TAG, "flushPendingBatchResults - scannerId=" + scannerId);
+        if (DBG) {
+            Log.d(TAG, "flushPendingBatchResults - scannerId=" + scannerId);
+        }
         mScanManager.flushBatchScanResults(new ScanClient(scannerId));
     }
 
     void stopScan(ScanClient client) {
         enforceAdminPermission();
-        int scanQueueSize = mScanManager.getBatchScanQueue().size() +
-                mScanManager.getRegularScanQueue().size();
-        if (DBG) Log.d(TAG, "stopScan() - queue size =" + scanQueueSize);
+        int scanQueueSize =
+                mScanManager.getBatchScanQueue().size() + mScanManager.getRegularScanQueue().size();
+        if (DBG) {
+            Log.d(TAG, "stopScan() - queue size =" + scanQueueSize);
+        }
 
         AppScanStats app = null;
         app = mScannerMap.getAppScanStatsById(client.scannerId);
-        if (app != null) app.recordScanStop(client.scannerId);
+        if (app != null) {
+            app.recordScanStop(client.scannerId);
+        }
 
         mScanManager.stopScan(client);
     }
@@ -1688,7 +2021,9 @@
         PendingIntentInfo pii = new PendingIntentInfo();
         pii.intent = intent;
         ScannerMap.App app = mScannerMap.getByContextInfo(pii);
-        if (VDBG) Log.d(TAG, "stopScan(PendingIntent): app found = " + app);
+        if (VDBG) {
+            Log.d(TAG, "stopScan(PendingIntent): app found = " + app);
+        }
         if (app != null) {
             final int scannerId = app.id;
             stopScan(new ScanClient(scannerId));
@@ -1698,10 +2033,14 @@
     }
 
     void disconnectAll() {
-        if (DBG) Log.d(TAG, "disconnectAll()");
+        if (DBG) {
+            Log.d(TAG, "disconnectAll()");
+        }
         Map<Integer, String> connMap = mClientMap.getConnectedMap();
-        for(Map.Entry<Integer, String> entry:connMap.entrySet()){
-            if (DBG) Log.d(TAG, "disconnecting addr:" + entry.getValue());
+        for (Map.Entry<Integer, String> entry : connMap.entrySet()) {
+            if (DBG) {
+                Log.d(TAG, "disconnecting addr:" + entry.getValue());
+            }
             clientDisconnect(entry.getKey(), entry.getValue());
             //clientDisconnect(int clientIf, String address)
         }
@@ -1709,7 +2048,9 @@
 
     void unregAll() {
         for (Integer appId : mClientMap.getAllAppsIds()) {
-            if (DBG) Log.d(TAG, "unreg:" + appId);
+            if (DBG) {
+                Log.d(TAG, "unreg:" + appId);
+            }
             unregisterClient(appId);
         }
     }
@@ -1717,8 +2058,8 @@
     /**************************************************************************
      * PERIODIC SCANNING
      *************************************************************************/
-    void registerSync(
-            ScanResult scanResult, int skip, int timeout, IPeriodicAdvertisingCallback callback) {
+    void registerSync(ScanResult scanResult, int skip, int timeout,
+            IPeriodicAdvertisingCallback callback) {
         enforceAdminPermission();
         mPeriodicScanManager.startSync(scanResult, skip, timeout, callback);
     }
@@ -1770,8 +2111,8 @@
         mAdvertiseManager.setAdvertisingParameters(advertiserId, parameters);
     }
 
-    void setPeriodicAdvertisingParameters(
-            int advertiserId, PeriodicAdvertisingParameters parameters) {
+    void setPeriodicAdvertisingParameters(int advertiserId,
+            PeriodicAdvertisingParameters parameters) {
         enforceAdminPermission();
         mAdvertiseManager.setPeriodicAdvertisingParameters(advertiserId, parameters);
     }
@@ -1793,16 +2134,19 @@
     void registerClient(UUID uuid, IBluetoothGattCallback callback) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "registerClient() - UUID=" + uuid);
+        if (DBG) {
+            Log.d(TAG, "registerClient() - UUID=" + uuid);
+        }
         mClientMap.add(uuid, null, callback, null, this);
-        gattClientRegisterAppNative(uuid.getLeastSignificantBits(),
-                                    uuid.getMostSignificantBits());
+        gattClientRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
     }
 
     void unregisterClient(int clientIf) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "unregisterClient() - clientIf=" + clientIf);
+        if (DBG) {
+            Log.d(TAG, "unregisterClient() - clientIf=" + clientIf);
+        }
         mClientMap.remove(clientIf);
         gattClientUnregisterAppNative(clientIf);
     }
@@ -1812,8 +2156,8 @@
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         if (DBG) {
-            Log.d(TAG, "clientConnect() - address=" + address + ", isDirect=" + isDirect +
-                    ", opportunistic=" + opportunistic + ", phy=" + phy);
+            Log.d(TAG, "clientConnect() - address=" + address + ", isDirect=" + isDirect
+                    + ", opportunistic=" + opportunistic + ", phy=" + phy);
         }
         gattClientConnectNative(clientIf, address, isDirect, transport, opportunistic, phy);
     }
@@ -1822,7 +2166,9 @@
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
-        if (DBG) Log.d(TAG, "clientDisconnect() - address=" + address + ", connId=" + connId);
+        if (DBG) {
+            Log.d(TAG, "clientDisconnect() - address=" + address + ", connId=" + connId);
+        }
 
         gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0);
     }
@@ -1832,11 +2178,15 @@
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
-            if (DBG) Log.d(TAG, "clientSetPreferredPhy() - no connection to " + address);
+            if (DBG) {
+                Log.d(TAG, "clientSetPreferredPhy() - no connection to " + address);
+            }
             return;
         }
 
-        if (DBG) Log.d(TAG, "clientSetPreferredPhy() - address=" + address + ", connId=" + connId);
+        if (DBG) {
+            Log.d(TAG, "clientSetPreferredPhy() - address=" + address + ", connId=" + connId);
+        }
         gattClientSetPreferredPhyNative(clientIf, address, txPhy, rxPhy, phyOptions);
     }
 
@@ -1845,17 +2195,21 @@
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
-            if (DBG) Log.d(TAG, "clientReadPhy() - no connection to " + address);
+            if (DBG) {
+                Log.d(TAG, "clientReadPhy() - no connection to " + address);
+            }
             return;
         }
 
-        if (DBG) Log.d(TAG, "clientReadPhy() - address=" + address + ", connId=" + connId);
+        if (DBG) {
+            Log.d(TAG, "clientReadPhy() - address=" + address + ", connId=" + connId);
+        }
         gattClientReadPhyNative(clientIf, address);
     }
 
     int numHwTrackFiltersAvailable() {
         return (AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements()
-                    - mScanManager.getCurrentUsedTrackingAdvertisement());
+                - mScanManager.getCurrentUsedTrackingAdvertisement());
     }
 
     synchronized List<ParcelUuid> getRegisteredServiceUuids() {
@@ -1880,7 +2234,9 @@
     void refreshDevice(int clientIf, String address) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "refreshDevice() - address=" + address);
+        if (DBG) {
+            Log.d(TAG, "refreshDevice() - address=" + address);
+        }
         gattClientRefreshNative(clientIf, address);
     }
 
@@ -1888,29 +2244,35 @@
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
-        if (DBG) Log.d(TAG, "discoverServices() - address=" + address + ", connId=" + connId);
+        if (DBG) {
+            Log.d(TAG, "discoverServices() - address=" + address + ", connId=" + connId);
+        }
 
-        if (connId != null)
+        if (connId != null) {
             gattClientSearchServiceNative(connId, true, 0, 0);
-        else
+        } else {
             Log.e(TAG, "discoverServices() - No connection for " + address + "...");
+        }
     }
 
     void discoverServiceByUuid(int clientIf, String address, UUID uuid) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
-        if (connId != null)
-            gattClientDiscoverServiceByUuidNative(
-                    connId, uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
-        else
+        if (connId != null) {
+            gattClientDiscoverServiceByUuidNative(connId, uuid.getLeastSignificantBits(),
+                    uuid.getMostSignificantBits());
+        } else {
             Log.e(TAG, "discoverServiceByUuid() - No connection for " + address + "...");
+        }
     }
 
     void readCharacteristic(int clientIf, String address, int handle, int authReq) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (VDBG) Log.d(TAG, "readCharacteristic() - address=" + address);
+        if (VDBG) {
+            Log.d(TAG, "readCharacteristic() - address=" + address);
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
@@ -1926,11 +2288,13 @@
         gattClientReadCharacteristicNative(connId, handle, authReq);
     }
 
-    void readUsingCharacteristicUuid(
-            int clientIf, String address, UUID uuid, int startHandle, int endHandle, int authReq) {
+    void readUsingCharacteristicUuid(int clientIf, String address, UUID uuid, int startHandle,
+            int endHandle, int authReq) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - address=" + address);
+        if (VDBG) {
+            Log.d(TAG, "readUsingCharacteristicUuid() - address=" + address);
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
@@ -1947,13 +2311,17 @@
                 uuid.getMostSignificantBits(), startHandle, endHandle, authReq);
     }
 
-    void writeCharacteristic(int clientIf, String address, int handle, int writeType,
-                             int authReq, byte[] value) {
+    void writeCharacteristic(int clientIf, String address, int handle, int writeType, int authReq,
+            byte[] value) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (VDBG) Log.d(TAG, "writeCharacteristic() - address=" + address);
+        if (VDBG) {
+            Log.d(TAG, "writeCharacteristic() - address=" + address);
+        }
 
-        if (mReliableQueue.contains(address)) writeType = 3; // Prepared write
+        if (mReliableQueue.contains(address)) {
+            writeType = 3; // Prepared write
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
@@ -1972,7 +2340,9 @@
     void readDescriptor(int clientIf, String address, int handle, int authReq) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (VDBG) Log.d(TAG, "readDescriptor() - address=" + address);
+        if (VDBG) {
+            Log.d(TAG, "readDescriptor() - address=" + address);
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
@@ -1986,12 +2356,15 @@
         }
 
         gattClientReadDescriptorNative(connId, handle, authReq);
-    };
+    }
 
-    void writeDescriptor(int clientIf, String address, int handle,
-                            int authReq, byte[] value) {
+    ;
+
+    void writeDescriptor(int clientIf, String address, int handle, int authReq, byte[] value) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (VDBG) Log.d(TAG, "writeDescriptor() - address=" + address);
+        if (VDBG) {
+            Log.d(TAG, "writeDescriptor() - address=" + address);
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
@@ -2010,25 +2383,32 @@
     void beginReliableWrite(int clientIf, String address) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "beginReliableWrite() - address=" + address);
+        if (DBG) {
+            Log.d(TAG, "beginReliableWrite() - address=" + address);
+        }
         mReliableQueue.add(address);
     }
 
     void endReliableWrite(int clientIf, String address, boolean execute) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "endReliableWrite() - address=" + address
-                                + " execute: " + execute);
+        if (DBG) {
+            Log.d(TAG, "endReliableWrite() - address=" + address + " execute: " + execute);
+        }
         mReliableQueue.remove(address);
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
-        if (connId != null) gattClientExecuteWriteNative(connId, execute);
+        if (connId != null) {
+            gattClientExecuteWriteNative(connId, execute);
+        }
     }
 
     void registerForNotification(int clientIf, String address, int handle, boolean enable) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "registerForNotification() - address=" + address + " enable: " + enable);
+        if (DBG) {
+            Log.d(TAG, "registerForNotification() - address=" + address + " enable: " + enable);
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
@@ -2047,14 +2427,18 @@
     void readRemoteRssi(int clientIf, String address) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "readRemoteRssi() - address=" + address);
+        if (DBG) {
+            Log.d(TAG, "readRemoteRssi() - address=" + address);
+        }
         gattClientReadRemoteRssiNative(clientIf, address);
     }
 
     void configureMTU(int clientIf, String address, int mtu) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "configureMTU() - address=" + address + " mtu=" + mtu);
+        if (DBG) {
+            Log.d(TAG, "configureMTU() - address=" + address + " mtu=" + mtu);
+        }
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId != null) {
             gattClientConfigureMTUNative(connId, mtu);
@@ -2075,8 +2459,7 @@
         // Link supervision timeout is measured in N * 10ms
         int timeout = 2000; // 20s
 
-        switch (connectionPriority)
-        {
+        switch (connectionPriority) {
             case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
                 minInterval = getResources().getInteger(R.integer.gatt_high_priority_min_interval);
                 maxInterval = getResources().getInteger(R.integer.gatt_high_priority_max_interval);
@@ -2099,10 +2482,31 @@
                 break;
         }
 
-        if (DBG) Log.d(TAG, "connectionParameterUpdate() - address=" + address
-            + "params=" + connectionPriority + " interval=" + minInterval + "/" + maxInterval);
+        if (DBG) {
+            Log.d(TAG, "connectionParameterUpdate() - address=" + address + "params="
+                    + connectionPriority + " interval=" + minInterval + "/" + maxInterval);
+        }
+        gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval, latency,
+                timeout, 0, 0);
+    }
+
+    void leConnectionUpdate(int clientIf, String address, int minInterval,
+                            int maxInterval, int slaveLatency,
+                            int supervisionTimeout, int minConnectionEventLen,
+                            int maxConnectionEventLen) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        if (DBG) {
+            Log.d(TAG, "leConnectionUpdate() - address=" + address + ", intervals="
+                        + minInterval + "/" + maxInterval + ", latency=" + slaveLatency
+                        + ", timeout=" + supervisionTimeout + "msec" + ", min_ce="
+                        + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen);
+
+
+        }
         gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval,
-                                            latency, timeout);
+                                            slaveLatency, supervisionTimeout,
+                                            minConnectionEventLen, maxConnectionEventLen);
     }
 
     /**************************************************************************
@@ -2113,7 +2517,9 @@
             throws RemoteException {
 
         UUID uuid = new UUID(uuidMsb, uuidLsb);
-        if (DBG) Log.d(TAG, "onServerRegistered() - UUID=" + uuid + ", serverIf=" + serverIf);
+        if (DBG) {
+            Log.d(TAG, "onServerRegistered() - UUID=" + uuid + ", serverIf=" + serverIf);
+        }
         ServerMap.App app = mServerMap.getByUuid(uuid);
         if (app != null) {
             app.id = serverIf;
@@ -2123,8 +2529,10 @@
     }
 
     void onServiceAdded(int status, int serverIf, List<GattDbElement> service)
-                        throws RemoteException {
-        if (DBG) Log.d(TAG, "onServiceAdded(), status=" + status);
+            throws RemoteException {
+        if (DBG) {
+            Log.d(TAG, "onServiceAdded(), status=" + status);
+        }
 
         if (status != 0) {
             return;
@@ -2148,46 +2556,55 @@
                         BluetoothGattService.SERVICE_TYPE_SECONDARY);
             } else if (el.type == GattDbElement.TYPE_CHARACTERISTIC) {
                 mHandleMap.addCharacteristic(serverIf, el.attributeHandle, el.uuid, srvcHandle);
-                svc.addCharacteristic(new BluetoothGattCharacteristic(el.uuid,
-                        el.attributeHandle, el.properties, el.permissions));
+                svc.addCharacteristic(
+                        new BluetoothGattCharacteristic(el.uuid, el.attributeHandle, el.properties,
+                                el.permissions));
             } else if (el.type == GattDbElement.TYPE_DESCRIPTOR) {
                 mHandleMap.addDescriptor(serverIf, el.attributeHandle, el.uuid, srvcHandle);
                 List<BluetoothGattCharacteristic> chars = svc.getCharacteristics();
-                chars.get(chars.size()-1).addDescriptor(
-                        new BluetoothGattDescriptor(el.uuid, el.attributeHandle, el.permissions));
+                chars.get(chars.size() - 1)
+                        .addDescriptor(new BluetoothGattDescriptor(el.uuid, el.attributeHandle,
+                                el.permissions));
             }
         }
         mHandleMap.setStarted(serverIf, srvcHandle, true);
 
         ServerMap.App app = mServerMap.getById(serverIf);
         if (app != null) {
-                app.callback.onServiceAdded(status, svc);
+            app.callback.onServiceAdded(status, svc);
         }
     }
 
-    void onServiceStopped(int status, int serverIf, int srvcHandle)
-            throws RemoteException {
-        if (DBG) Log.d(TAG, "onServiceStopped() srvcHandle=" + srvcHandle
-            + ", status=" + status);
-        if (status == 0)
+    void onServiceStopped(int status, int serverIf, int srvcHandle) throws RemoteException {
+        if (DBG) {
+            Log.d(TAG, "onServiceStopped() srvcHandle=" + srvcHandle + ", status=" + status);
+        }
+        if (status == 0) {
             mHandleMap.setStarted(serverIf, srvcHandle, false);
+        }
         stopNextService(serverIf, status);
     }
 
     void onServiceDeleted(int status, int serverIf, int srvcHandle) {
-        if (DBG) Log.d(TAG, "onServiceDeleted() srvcHandle=" + srvcHandle
-            + ", status=" + status);
+        if (DBG) {
+            Log.d(TAG, "onServiceDeleted() srvcHandle=" + srvcHandle + ", status=" + status);
+        }
         mHandleMap.deleteService(serverIf, srvcHandle);
     }
 
     void onClientConnected(String address, boolean connected, int connId, int serverIf)
             throws RemoteException {
 
-        if (DBG) Log.d(TAG, "onClientConnected() connId=" + connId
-            + ", address=" + address + ", connected=" + connected);
+        if (DBG) {
+            Log.d(TAG,
+                    "onClientConnected() connId=" + connId + ", address=" + address + ", connected="
+                            + connected);
+        }
 
         ServerMap.App app = mServerMap.getById(serverIf);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         if (connected) {
             mServerMap.addConnection(serverIf, connId, address);
@@ -2195,112 +2612,137 @@
             mServerMap.removeConnection(serverIf, connId);
         }
 
-        app.callback.onServerConnectionState((byte)0, serverIf, connected, address);
+        app.callback.onServerConnectionState((byte) 0, serverIf, connected, address);
     }
 
-    void onServerReadCharacteristic(String address, int connId, int transId,
-                            int handle, int offset, boolean isLong)
-                            throws RemoteException {
-        if (VDBG) Log.d(TAG, "onServerReadCharacteristic() connId=" + connId
-            + ", address=" + address + ", handle=" + handle
-            + ", requestId=" + transId + ", offset=" + offset);
+    void onServerReadCharacteristic(String address, int connId, int transId, int handle, int offset,
+            boolean isLong) throws RemoteException {
+        if (VDBG) {
+            Log.d(TAG, "onServerReadCharacteristic() connId=" + connId + ", address=" + address
+                    + ", handle=" + handle + ", requestId=" + transId + ", offset=" + offset);
+        }
 
         HandleMap.Entry entry = mHandleMap.getByHandle(handle);
-        if (entry == null) return;
+        if (entry == null) {
+            return;
+        }
 
         mHandleMap.addRequest(transId, handle);
 
         ServerMap.App app = mServerMap.getById(entry.serverIf);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onCharacteristicReadRequest(address, transId, offset, isLong, handle);
     }
 
-    void onServerReadDescriptor(String address, int connId, int transId,
-                            int handle, int offset, boolean isLong)
-                            throws RemoteException {
-        if (VDBG) Log.d(TAG, "onServerReadDescriptor() connId=" + connId
-            + ", address=" + address + ", handle=" + handle
-            + ", requestId=" + transId + ", offset=" + offset);
+    void onServerReadDescriptor(String address, int connId, int transId, int handle, int offset,
+            boolean isLong) throws RemoteException {
+        if (VDBG) {
+            Log.d(TAG, "onServerReadDescriptor() connId=" + connId + ", address=" + address
+                    + ", handle=" + handle + ", requestId=" + transId + ", offset=" + offset);
+        }
 
         HandleMap.Entry entry = mHandleMap.getByHandle(handle);
-        if (entry == null) return;
+        if (entry == null) {
+            return;
+        }
 
         mHandleMap.addRequest(transId, handle);
 
         ServerMap.App app = mServerMap.getById(entry.serverIf);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onDescriptorReadRequest(address, transId, offset, isLong, handle);
     }
 
-    void onServerWriteCharacteristic(String address, int connId, int transId,
-                            int handle, int offset, int length,
-                            boolean needRsp, boolean isPrep,
-                            byte[] data)
-                            throws RemoteException {
-        if (VDBG) Log.d(TAG, "onServerWriteCharacteristic() connId=" + connId
-            + ", address=" + address + ", handle=" + handle
-            + ", requestId=" + transId + ", isPrep=" + isPrep
-            + ", offset=" + offset);
+    void onServerWriteCharacteristic(String address, int connId, int transId, int handle,
+            int offset, int length, boolean needRsp, boolean isPrep, byte[] data)
+            throws RemoteException {
+        if (VDBG) {
+            Log.d(TAG, "onServerWriteCharacteristic() connId=" + connId + ", address=" + address
+                    + ", handle=" + handle + ", requestId=" + transId + ", isPrep=" + isPrep
+                    + ", offset=" + offset);
+        }
 
         HandleMap.Entry entry = mHandleMap.getByHandle(handle);
-        if (entry == null) return;
+        if (entry == null) {
+            return;
+        }
 
         mHandleMap.addRequest(transId, handle);
 
         ServerMap.App app = mServerMap.getById(entry.serverIf);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
-        app.callback.onCharacteristicWriteRequest(address, transId,
-                    offset, length, isPrep, needRsp, handle, data);
+        app.callback.onCharacteristicWriteRequest(address, transId, offset, length, isPrep, needRsp,
+                handle, data);
     }
 
-    void onServerWriteDescriptor(String address, int connId, int transId,
-                            int handle, int offset, int length,
-                            boolean needRsp, boolean isPrep,
-                            byte[] data)
-                            throws RemoteException {
-        if (VDBG) Log.d(TAG, "onAttributeWrite() connId=" + connId
-            + ", address=" + address + ", handle=" + handle
-            + ", requestId=" + transId + ", isPrep=" + isPrep
-            + ", offset=" + offset);
+    void onServerWriteDescriptor(String address, int connId, int transId, int handle, int offset,
+            int length, boolean needRsp, boolean isPrep, byte[] data) throws RemoteException {
+        if (VDBG) {
+            Log.d(TAG, "onAttributeWrite() connId=" + connId + ", address=" + address + ", handle="
+                    + handle + ", requestId=" + transId + ", isPrep=" + isPrep + ", offset="
+                    + offset);
+        }
 
         HandleMap.Entry entry = mHandleMap.getByHandle(handle);
-        if (entry == null) return;
+        if (entry == null) {
+            return;
+        }
 
         mHandleMap.addRequest(transId, handle);
 
         ServerMap.App app = mServerMap.getById(entry.serverIf);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
-        app.callback.onDescriptorWriteRequest(address, transId,
-                    offset, length, isPrep, needRsp, handle, data);
+        app.callback.onDescriptorWriteRequest(address, transId, offset, length, isPrep, needRsp,
+                handle, data);
     }
 
     void onExecuteWrite(String address, int connId, int transId, int execWrite)
             throws RemoteException {
-        if (DBG) Log.d(TAG, "onExecuteWrite() connId=" + connId
-            + ", address=" + address + ", transId=" + transId);
+        if (DBG) {
+            Log.d(TAG, "onExecuteWrite() connId=" + connId + ", address=" + address + ", transId="
+                    + transId);
+        }
 
         ServerMap.App app = mServerMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onExecuteWrite(address, transId, execWrite == 1);
     }
 
     void onResponseSendCompleted(int status, int attrHandle) {
-        if (DBG) Log.d(TAG, "onResponseSendCompleted() handle=" + attrHandle);
+        if (DBG) {
+            Log.d(TAG, "onResponseSendCompleted() handle=" + attrHandle);
+        }
     }
 
     void onNotificationSent(int connId, int status) throws RemoteException {
-        if (VDBG) Log.d(TAG, "onNotificationSent() connId=" + connId + ", status=" + status);
+        if (VDBG) {
+            Log.d(TAG, "onNotificationSent() connId=" + connId + ", status=" + status);
+        }
 
         String address = mServerMap.addressByConnId(connId);
-        if (address == null) return;
+        if (address == null) {
+            return;
+        }
 
         ServerMap.App app = mServerMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         if (!app.isCongested) {
             app.callback.onNotificationSent(address, status);
@@ -2313,27 +2755,39 @@
     }
 
     void onServerCongestion(int connId, boolean congested) throws RemoteException {
-        if (DBG) Log.d(TAG, "onServerCongestion() - connId=" + connId + ", congested=" + congested);
+        if (DBG) {
+            Log.d(TAG, "onServerCongestion() - connId=" + connId + ", congested=" + congested);
+        }
 
         ServerMap.App app = mServerMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.isCongested = congested;
-        while(!app.isCongested) {
+        while (!app.isCongested) {
             CallbackInfo callbackInfo = app.popQueuedCallback();
-            if (callbackInfo == null) return;
+            if (callbackInfo == null) {
+                return;
+            }
             app.callback.onNotificationSent(callbackInfo.address, callbackInfo.status);
         }
     }
 
     void onMtuChanged(int connId, int mtu) throws RemoteException {
-        if (DBG) Log.d(TAG, "onMtuChanged() - connId=" + connId + ", mtu=" + mtu);
+        if (DBG) {
+            Log.d(TAG, "onMtuChanged() - connId=" + connId + ", mtu=" + mtu);
+        }
 
         String address = mServerMap.addressByConnId(connId);
-        if (address == null) return;
+        if (address == null) {
+            return;
+        }
 
         ServerMap.App app = mServerMap.getByConnId(connId);
-        if (app == null) return;
+        if (app == null) {
+            return;
+        }
 
         app.callback.onMtuChanged(address, mtu);
     }
@@ -2345,16 +2799,19 @@
     void registerServer(UUID uuid, IBluetoothGattServerCallback callback) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "registerServer() - UUID=" + uuid);
+        if (DBG) {
+            Log.d(TAG, "registerServer() - UUID=" + uuid);
+        }
         mServerMap.add(uuid, null, callback, null, this);
-        gattServerRegisterAppNative(uuid.getLeastSignificantBits(),
-                                    uuid.getMostSignificantBits());
+        gattServerRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
     }
 
     void unregisterServer(int serverIf) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "unregisterServer() - serverIf=" + serverIf);
+        if (DBG) {
+            Log.d(TAG, "unregisterServer() - serverIf=" + serverIf);
+        }
 
         deleteServices(serverIf);
 
@@ -2365,15 +2822,19 @@
     void serverConnect(int serverIf, String address, boolean isDirect, int transport) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "serverConnect() - address=" + address);
-        gattServerConnectNative(serverIf, address, isDirect,transport);
+        if (DBG) {
+            Log.d(TAG, "serverConnect() - address=" + address);
+        }
+        gattServerConnectNative(serverIf, address, isDirect, transport);
     }
 
     void serverDisconnect(int serverIf, String address) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         Integer connId = mServerMap.connIdByAddress(serverIf, address);
-        if (DBG) Log.d(TAG, "serverDisconnect() - address=" + address + ", connId=" + connId);
+        if (DBG) {
+            Log.d(TAG, "serverDisconnect() - address=" + address + ", connId=" + connId);
+        }
 
         gattServerDisconnectNative(serverIf, address, connId != null ? connId : 0);
     }
@@ -2383,11 +2844,15 @@
 
         Integer connId = mServerMap.connIdByAddress(serverIf, address);
         if (connId == null) {
-            if (DBG) Log.d(TAG, "serverSetPreferredPhy() - no connection to " + address);
+            if (DBG) {
+                Log.d(TAG, "serverSetPreferredPhy() - no connection to " + address);
+            }
             return;
         }
 
-        if (DBG) Log.d(TAG, "serverSetPreferredPhy() - address=" + address + ", connId=" + connId);
+        if (DBG) {
+            Log.d(TAG, "serverSetPreferredPhy() - address=" + address + ", connId=" + connId);
+        }
         gattServerSetPreferredPhyNative(serverIf, address, txPhy, rxPhy, phyOptions);
     }
 
@@ -2396,42 +2861,55 @@
 
         Integer connId = mServerMap.connIdByAddress(serverIf, address);
         if (connId == null) {
-            if (DBG) Log.d(TAG, "serverReadPhy() - no connection to " + address);
+            if (DBG) {
+                Log.d(TAG, "serverReadPhy() - no connection to " + address);
+            }
             return;
         }
 
-        if (DBG) Log.d(TAG, "serverReadPhy() - address=" + address + ", connId=" + connId);
+        if (DBG) {
+            Log.d(TAG, "serverReadPhy() - address=" + address + ", connId=" + connId);
+        }
         gattServerReadPhyNative(serverIf, address);
     }
 
     void addService(int serverIf, BluetoothGattService service) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "addService() - uuid=" + service.getUuid());
+        if (DBG) {
+            Log.d(TAG, "addService() - uuid=" + service.getUuid());
+        }
 
         List<GattDbElement> db = new ArrayList<GattDbElement>();
 
-        if (service.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY)
+        if (service.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY) {
             db.add(GattDbElement.createPrimaryService(service.getUuid()));
-        else db.add(GattDbElement.createSecondaryService(service.getUuid()));
-
-        for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
-            int permission = ((characteristic.getKeySize() - 7) << 12)
-                                    + characteristic.getPermissions();
-            db.add(GattDbElement.createCharacteristic(characteristic.getUuid(),
-                 characteristic.getProperties(), permission));
-
-            for (BluetoothGattDescriptor descriptor: characteristic.getDescriptors()) {
-                permission = ((characteristic.getKeySize() - 7) << 12)
-                                    + descriptor.getPermissions();
-                db.add(GattDbElement.createDescriptor(descriptor.getUuid(), permission));
-            }
+        } else {
+            db.add(GattDbElement.createSecondaryService(service.getUuid()));
         }
 
         for (BluetoothGattService includedService : service.getIncludedServices()) {
-            int inclSrvc = mHandleMap.getServiceHandle(includedService.getUuid(),
-                    includedService.getType(), includedService.getInstanceId());
-            db.add(GattDbElement.createIncludedService(inclSrvc));
+            int inclSrvcHandle = includedService.getInstanceId();
+
+            if (mHandleMap.checkServiceExists(includedService.getUuid(), inclSrvcHandle)) {
+                db.add(GattDbElement.createIncludedService(inclSrvcHandle));
+            } else {
+                Log.e(TAG,
+                        "included service with UUID " + includedService.getUuid() + " not found!");
+            }
+        }
+
+        for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
+            int permission =
+                    ((characteristic.getKeySize() - 7) << 12) + characteristic.getPermissions();
+            db.add(GattDbElement.createCharacteristic(characteristic.getUuid(),
+                    characteristic.getProperties(), permission));
+
+            for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
+                permission =
+                        ((characteristic.getKeySize() - 7) << 12) + descriptor.getPermissions();
+                db.add(GattDbElement.createDescriptor(descriptor.getUuid(), permission));
+            }
         }
 
         gattServerAddServiceNative(serverIf, db);
@@ -2440,7 +2918,9 @@
     void removeService(int serverIf, int handle) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "removeService() - handle=" + handle);
+        if (DBG) {
+            Log.d(TAG, "removeService() - handle=" + handle);
+        }
 
         gattServerDeleteServiceNative(serverIf, handle);
     }
@@ -2448,33 +2928,43 @@
     void clearServices(int serverIf) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (DBG) Log.d(TAG, "clearServices()");
+        if (DBG) {
+            Log.d(TAG, "clearServices()");
+        }
         deleteServices(serverIf);
     }
 
-    void sendResponse(int serverIf, String address, int requestId,
-                      int status, int offset, byte[] value) {
+    void sendResponse(int serverIf, String address, int requestId, int status, int offset,
+            byte[] value) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (VDBG) Log.d(TAG, "sendResponse() - address=" + address);
+        if (VDBG) {
+            Log.d(TAG, "sendResponse() - address=" + address);
+        }
 
         int handle = 0;
         HandleMap.Entry entry = mHandleMap.getByRequestId(requestId);
-        if (entry != null) handle = entry.handle;
+        if (entry != null) {
+            handle = entry.handle;
+        }
 
-        int connId = mServerMap.connIdByAddress(serverIf, address);
-        gattServerSendResponseNative(serverIf, connId, requestId, (byte)status,
-                                     handle, offset, value, (byte)0);
+        Integer connId = mServerMap.connIdByAddress(serverIf, address);
+        gattServerSendResponseNative(serverIf, connId != null ? connId : 0, requestId,
+                (byte) status, handle, offset, value, (byte) 0);
         mHandleMap.deleteRequest(requestId);
     }
 
     void sendNotification(int serverIf, String address, int handle, boolean confirm, byte[] value) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
-        if (VDBG) Log.d(TAG, "sendNotification() - address=" + address + " handle=" + handle);
+        if (VDBG) {
+            Log.d(TAG, "sendNotification() - address=" + address + " handle=" + handle);
+        }
 
-        int connId = mServerMap.connIdByAddress(serverIf, address);
-        if (connId == 0) return;
+        Integer connId = mServerMap.connIdByAddress(serverIf, address);
+        if (connId == null || connId == 0) {
+            return;
+        }
 
         if (confirm) {
             gattServerSendIndicationNative(serverIf, handle, connId, value);
@@ -2489,31 +2979,36 @@
      *************************************************************************/
 
     private boolean isRestrictedCharUuid(final UUID charUuid) {
-      return isHidUuid(charUuid);
+        return isHidUuid(charUuid);
     }
 
     private boolean isRestrictedSrvcUuid(final UUID srvcUuid) {
-      return isFidoUUID(srvcUuid);
+        return isFidoUUID(srvcUuid);
     }
 
     private boolean isHidUuid(final UUID uuid) {
-        for (UUID hid_uuid : HID_UUIDS) {
-            if (hid_uuid.equals(uuid)) return true;
+        for (UUID hidUuid : HID_UUIDS) {
+            if (hidUuid.equals(uuid)) {
+                return true;
+            }
         }
         return false;
     }
 
     private boolean isFidoUUID(final UUID uuid) {
-        for (UUID fido_uuid : FIDO_UUIDS) {
-            if (fido_uuid.equals(uuid)) return true;
+        for (UUID fidoUuid : FIDO_UUIDS) {
+            if (fidoUuid.equals(uuid)) {
+                return true;
+            }
         }
         return false;
     }
 
     private int getDeviceType(BluetoothDevice device) {
         int type = gattClientGetDeviceTypeNative(device.getAddress());
-        if (DBG) Log.d(TAG, "getDeviceType() - device=" + device
-            + ", type=" + type);
+        if (DBG) {
+            Log.d(TAG, "getDeviceType() - device=" + device + ", type=" + type);
+        }
         return type;
     }
 
@@ -2524,13 +3019,19 @@
     private boolean needsPrivilegedPermissionForScan(ScanSettings settings) {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         // BLE scan only mode needs special permission.
-        if (adapter.getState() != BluetoothAdapter.STATE_ON) return true;
+        if (adapter.getState() != BluetoothAdapter.STATE_ON) {
+            return true;
+        }
 
         // Regular scan, no special permission.
-        if (settings == null) return false;
+        if (settings == null) {
+            return false;
+        }
 
         // Regular scan, no special permission.
-        if (settings.getReportDelayMillis() == 0) return false;
+        if (settings.getReportDelayMillis() == 0) {
+            return false;
+        }
 
         // Batch scan, truncated mode needs permission.
         return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_ABBREVIATED;
@@ -2540,7 +3041,7 @@
     // thrown if the caller app does not have BLUETOOTH_PRIVILEGED permission.
     private void enforcePrivilegedPermission() {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
-            "Need BLUETOOTH_PRIVILEGED permission");
+                "Need BLUETOOTH_PRIVILEGED permission");
     }
 
     // Enforce caller has UPDATE_DEVICE_STATS permission, which allows the caller to blame other
@@ -2552,16 +3053,17 @@
     }
 
     private void stopNextService(int serverIf, int status) throws RemoteException {
-        if (DBG) Log.d(TAG, "stopNextService() - serverIf=" + serverIf
-            + ", status=" + status);
+        if (DBG) {
+            Log.d(TAG, "stopNextService() - serverIf=" + serverIf + ", status=" + status);
+        }
 
         if (status == 0) {
             List<HandleMap.Entry> entries = mHandleMap.getEntries();
-            for(HandleMap.Entry entry : entries) {
-                if (entry.type != HandleMap.TYPE_SERVICE ||
-                    entry.serverIf != serverIf ||
-                    entry.started == false)
-                        continue;
+            for (HandleMap.Entry entry : entries) {
+                if (entry.type != HandleMap.TYPE_SERVICE || entry.serverIf != serverIf
+                        || !entry.started) {
+                    continue;
+                }
 
                 gattServerStopServiceNative(serverIf, entry.handle);
                 return;
@@ -2570,7 +3072,9 @@
     }
 
     private void deleteServices(int serverIf) {
-        if (DBG) Log.d(TAG, "deleteServices() - serverIf=" + serverIf);
+        if (DBG) {
+            Log.d(TAG, "deleteServices() - serverIf=" + serverIf);
+        }
 
         /*
          * Figure out which handles to delete.
@@ -2578,37 +3082,39 @@
          */
         List<Integer> handleList = new ArrayList<Integer>();
         List<HandleMap.Entry> entries = mHandleMap.getEntries();
-        for(HandleMap.Entry entry : entries) {
-            if (entry.type != HandleMap.TYPE_SERVICE ||
-                entry.serverIf != serverIf)
-                    continue;
+        for (HandleMap.Entry entry : entries) {
+            if (entry.type != HandleMap.TYPE_SERVICE || entry.serverIf != serverIf) {
+                continue;
+            }
             handleList.add(entry.handle);
         }
 
         /* Now actually delete the services.... */
-        for(Integer handle : handleList) {
+        for (Integer handle : handleList) {
             gattServerDeleteServiceNative(serverIf, handle);
         }
     }
 
-    private List<UUID> parseUuids(byte[] adv_data) {
+    private List<UUID> parseUuids(byte[] advData) {
         List<UUID> uuids = new ArrayList<UUID>();
 
         int offset = 0;
-        while(offset < (adv_data.length-2)) {
-            int len = Byte.toUnsignedInt(adv_data[offset++]);
-            if (len == 0) break;
+        while (offset < (advData.length - 2)) {
+            int len = Byte.toUnsignedInt(advData[offset++]);
+            if (len == 0) {
+                break;
+            }
 
-            int type = adv_data[offset++];
+            int type = advData[offset++];
             switch (type) {
                 case 0x02: // Partial list of 16-bit UUIDs
                 case 0x03: // Complete list of 16-bit UUIDs
                     while (len > 1) {
-                        int uuid16 = adv_data[offset++];
-                        uuid16 += (adv_data[offset++] << 8);
+                        int uuid16 = advData[offset++];
+                        uuid16 += (advData[offset++] << 8);
                         len -= 2;
-                        uuids.add(UUID.fromString(String.format(
-                            "%08x-0000-1000-8000-00805f9b34fb", uuid16)));
+                        uuids.add(UUID.fromString(
+                                String.format("%08x-0000-1000-8000-00805f9b34fb", uuid16)));
                     }
                     break;
 
@@ -2644,28 +3150,19 @@
         mHandleMap.dump(sb);
     }
 
-    void addScanResult() {
-        if (mScanEvents.isEmpty())
-            return;
-
-        BluetoothProto.ScanEvent curr = mScanEvents.get(mScanEvents.size() - 1);
-        curr.setNumberResults(curr.getNumberResults() + 1);
-    }
-
-    void addScanEvent(BluetoothProto.ScanEvent event) {
-        synchronized(mScanEvents) {
-            if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT)
+    void addScanEvent(BluetoothMetricsProto.ScanEvent event) {
+        synchronized (mScanEvents) {
+            if (mScanEvents.size() == NUM_SCAN_EVENTS_KEPT) {
                 mScanEvents.remove(0);
+            }
             mScanEvents.add(event);
         }
     }
 
     @Override
-    public void dumpProto(BluetoothProto.BluetoothLog proto) {
-        synchronized(mScanEvents) {
-            for (BluetoothProto.ScanEvent event : mScanEvents) {
-                proto.addScanEvent(event);
-            }
+    public void dumpProto(BluetoothMetricsProto.BluetoothLog.Builder builder) {
+        synchronized (mScanEvents) {
+            builder.addAllScanEvent(mScanEvents);
         }
     }
 
@@ -2673,113 +3170,110 @@
      * GATT Test functions
      *************************************************************************/
 
-    void gattTestCommand(int command, UUID uuid1, String bda1,
-                         int p1, int p2, int p3, int p4, int p5) {
-        if (bda1 == null) bda1 = "00:00:00:00:00:00";
-        if (uuid1 != null)
-            gattTestNative(command, uuid1.getLeastSignificantBits(),
-                       uuid1.getMostSignificantBits(), bda1, p1, p2, p3, p4, p5);
-        else
-            gattTestNative(command, 0,0, bda1, p1, p2, p3, p4, p5);
+    void gattTestCommand(int command, UUID uuid1, String bda1, int p1, int p2, int p3, int p4,
+            int p5) {
+        if (bda1 == null) {
+            bda1 = "00:00:00:00:00:00";
+        }
+        if (uuid1 != null) {
+            gattTestNative(command, uuid1.getLeastSignificantBits(), uuid1.getMostSignificantBits(),
+                    bda1, p1, p2, p3, p4, p5);
+        } else {
+            gattTestNative(command, 0, 0, bda1, p1, p2, p3, p4, p5);
+        }
     }
 
-    private native void gattTestNative(int command,
-                                    long uuid1_lsb, long uuid1_msb, String bda1,
-                                    int p1, int p2, int p3, int p4, int p5);
+    private native void gattTestNative(int command, long uuid1Lsb, long uuid1Msb, String bda1,
+            int p1, int p2, int p3, int p4, int p5);
 
     /**************************************************************************
      * Native functions prototypes
      *************************************************************************/
 
-    private native static void classInitNative();
+    private static native void classInitNative();
+
     private native void initializeNative();
+
     private native void cleanupNative();
 
     private native int gattClientGetDeviceTypeNative(String address);
 
-    private native void gattClientRegisterAppNative(long app_uuid_lsb,
-                                                    long app_uuid_msb);
+    private native void gattClientRegisterAppNative(long appUuidLsb, long appUuidMsb);
 
     private native void gattClientUnregisterAppNative(int clientIf);
 
     private native void gattClientConnectNative(int clientIf, String address, boolean isDirect,
-            int transport, boolean opportunistic, int initiating_phys);
+            int transport, boolean opportunistic, int initiatingPhys);
 
-    private native void gattClientDisconnectNative(int clientIf, String address,
-            int conn_id);
+    private native void gattClientDisconnectNative(int clientIf, String address, int connId);
 
-    private native void gattClientSetPreferredPhyNative(
-            int clientIf, String address, int tx_phy, int rx_phy, int phy_options);
+    private native void gattClientSetPreferredPhyNative(int clientIf, String address, int txPhy,
+            int rxPhy, int phyOptions);
 
     private native void gattClientReadPhyNative(int clientIf, String address);
 
     private native void gattClientRefreshNative(int clientIf, String address);
 
-    private native void gattClientSearchServiceNative(int conn_id,
-            boolean search_all, long service_uuid_lsb, long service_uuid_msb);
+    private native void gattClientSearchServiceNative(int connId, boolean searchAll,
+            long serviceUuidLsb, long serviceUuidMsb);
 
-    private native void gattClientDiscoverServiceByUuidNative(
-            int conn_id, long service_uuid_lsb, long service_uuid_msb);
+    private native void gattClientDiscoverServiceByUuidNative(int connId, long serviceUuidLsb,
+            long serviceUuidMsb);
 
-    private native void gattClientGetGattDbNative(int conn_id);
+    private native void gattClientGetGattDbNative(int connId);
 
-    private native void gattClientReadCharacteristicNative(int conn_id, int handle, int authReq);
+    private native void gattClientReadCharacteristicNative(int connId, int handle, int authReq);
 
-    private native void gattClientReadUsingCharacteristicUuidNative(
-            int conn_id, long uuid_msb, long uuid_lsb, int s_handle, int e_handle, int authReq);
+    private native void gattClientReadUsingCharacteristicUuidNative(int connId, long uuidMsb,
+            long uuidLsb, int sHandle, int eHandle, int authReq);
 
-    private native void gattClientReadDescriptorNative(int conn_id, int handle, int authReq);
+    private native void gattClientReadDescriptorNative(int connId, int handle, int authReq);
 
-    private native void gattClientWriteCharacteristicNative(int conn_id,
-            int handle, int write_type, int auth_req, byte[] value);
+    private native void gattClientWriteCharacteristicNative(int connId, int handle, int writeType,
+            int authReq, byte[] value);
 
-    private native void gattClientWriteDescriptorNative(int conn_id, int handle,
-            int auth_req, byte[] value);
+    private native void gattClientWriteDescriptorNative(int connId, int handle, int authReq,
+            byte[] value);
 
-    private native void gattClientExecuteWriteNative(int conn_id, boolean execute);
+    private native void gattClientExecuteWriteNative(int connId, boolean execute);
 
-    private native void gattClientRegisterForNotificationsNative(int clientIf,
-            String address, int handle, boolean enable);
+    private native void gattClientRegisterForNotificationsNative(int clientIf, String address,
+            int handle, boolean enable);
 
-    private native void gattClientReadRemoteRssiNative(int clientIf,
-            String address);
+    private native void gattClientReadRemoteRssiNative(int clientIf, String address);
 
-    private native void gattClientConfigureMTUNative(int conn_id, int mtu);
+    private native void gattClientConfigureMTUNative(int connId, int mtu);
 
-    private native void gattConnectionParameterUpdateNative(int client_if, String address,
-            int minInterval, int maxInterval, int latency, int timeout);
+    private native void gattConnectionParameterUpdateNative(int clientIf, String address,
+            int minInterval, int maxInterval, int latency, int timeout, int minConnectionEventLen,
+            int maxConnectionEventLen);
 
-    private native void gattServerRegisterAppNative(long app_uuid_lsb,
-                                                    long app_uuid_msb);
+    private native void gattServerRegisterAppNative(long appUuidLsb, long appUuidMsb);
 
     private native void gattServerUnregisterAppNative(int serverIf);
 
-    private native void gattServerConnectNative(int server_if, String address,
-                                             boolean is_direct, int transport);
+    private native void gattServerConnectNative(int serverIf, String address, boolean isDirect,
+            int transport);
 
-    private native void gattServerDisconnectNative(int serverIf, String address,
-                                              int conn_id);
+    private native void gattServerDisconnectNative(int serverIf, String address, int connId);
 
-    private native void gattServerSetPreferredPhyNative(
-            int clientIf, String address, int tx_phy, int rx_phy, int phy_options);
+    private native void gattServerSetPreferredPhyNative(int clientIf, String address, int txPhy,
+            int rxPhy, int phyOptions);
 
     private native void gattServerReadPhyNative(int clientIf, String address);
 
-    private native void gattServerAddServiceNative(int server_if, List<GattDbElement> service);
+    private native void gattServerAddServiceNative(int serverIf, List<GattDbElement> service);
 
-    private native void gattServerStopServiceNative (int server_if,
-                                                     int svc_handle);
+    private native void gattServerStopServiceNative(int serverIf, int svcHandle);
 
-    private native void gattServerDeleteServiceNative (int server_if,
-                                                       int svc_handle);
+    private native void gattServerDeleteServiceNative(int serverIf, int svcHandle);
 
-    private native void gattServerSendIndicationNative (int server_if,
-            int attr_handle, int conn_id, byte[] val);
+    private native void gattServerSendIndicationNative(int serverIf, int attrHandle, int connId,
+            byte[] val);
 
-    private native void gattServerSendNotificationNative (int server_if,
-            int attr_handle, int conn_id, byte[] val);
+    private native void gattServerSendNotificationNative(int serverIf, int attrHandle, int connId,
+            byte[] val);
 
-    private native void gattServerSendResponseNative (int server_if,
-            int conn_id, int trans_id, int status, int handle, int offset,
-            byte[] val, int auth_req);
+    private native void gattServerSendResponseNative(int serverIf, int connId, int transId,
+            int status, int handle, int offset, byte[] val, int authReq);
 }
diff --git a/src/com/android/bluetooth/gatt/HandleMap.java b/src/com/android/bluetooth/gatt/HandleMap.java
index 27a7a71..2b662d6 100644
--- a/src/com/android/bluetooth/gatt/HandleMap.java
+++ b/src/com/android/bluetooth/gatt/HandleMap.java
@@ -16,6 +16,7 @@
 package com.android.bluetooth.gatt;
 
 import android.util.Log;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -33,16 +34,16 @@
     public static final int TYPE_DESCRIPTOR = 3;
 
     class Entry {
-        int serverIf = 0;
-        int type = TYPE_UNDEFINED;
-        int handle = 0;
-        UUID uuid = null;
-        int instance = 0;
-        int serviceType = 0;
-        int serviceHandle = 0;
-        int charHandle = 0;
-        boolean started = false;
-        boolean advertisePreferred = false;
+        public int serverIf = 0;
+        public int type = TYPE_UNDEFINED;
+        public int handle = 0;
+        public UUID uuid = null;
+        public int instance = 0;
+        public int serviceType = 0;
+        public int serviceHandle = 0;
+        public int charHandle = 0;
+        public boolean started = false;
+        public boolean advertisePreferred = false;
 
         Entry(int serverIf, int handle, UUID uuid, int serviceType, int instance) {
             this.serverIf = serverIf;
@@ -54,7 +55,7 @@
         }
 
         Entry(int serverIf, int handle, UUID uuid, int serviceType, int instance,
-            boolean advertisePreferred) {
+                boolean advertisePreferred) {
             this.serverIf = serverIf;
             this.type = TYPE_SERVICE;
             this.handle = handle;
@@ -97,7 +98,7 @@
     }
 
     void addService(int serverIf, int handle, UUID uuid, int serviceType, int instance,
-        boolean advertisePreferred) {
+            boolean advertisePreferred) {
         mEntries.add(new Entry(serverIf, handle, uuid, serviceType, instance, advertisePreferred));
     }
 
@@ -107,15 +108,16 @@
     }
 
     void addDescriptor(int serverIf, int handle, UUID uuid, int serviceHandle) {
-        mEntries.add(new Entry(serverIf, TYPE_DESCRIPTOR, handle, uuid, serviceHandle, mLastCharacteristic));
+        mEntries.add(new Entry(serverIf, TYPE_DESCRIPTOR, handle, uuid, serviceHandle,
+                mLastCharacteristic));
     }
 
     void setStarted(int serverIf, int handle, boolean started) {
-        for(Entry entry : mEntries) {
-            if (entry.type != TYPE_SERVICE ||
-                entry.serverIf != serverIf ||
-                entry.handle != handle)
+        for (Entry entry : mEntries) {
+            if (entry.type != TYPE_SERVICE || entry.serverIf != serverIf
+                    || entry.handle != handle) {
                 continue;
+            }
 
             entry.started = started;
             return;
@@ -123,49 +125,34 @@
     }
 
     Entry getByHandle(int handle) {
-        for(Entry entry : mEntries) {
-            if (entry.handle == handle)
+        for (Entry entry : mEntries) {
+            if (entry.handle == handle) {
                 return entry;
+            }
         }
         Log.e(TAG, "getByHandle() - Handle " + handle + " not found!");
         return null;
     }
 
-    int getServiceHandle(UUID uuid, int serviceType, int instance) {
-        for(Entry entry : mEntries) {
-            if (entry.type == TYPE_SERVICE &&
-                entry.serviceType == serviceType &&
-                entry.instance == instance &&
-                entry.uuid.equals(uuid)) {
-                return entry.handle;
+    boolean checkServiceExists(UUID uuid, int handle) {
+        for (Entry entry : mEntries) {
+            if (entry.type == TYPE_SERVICE && entry.handle == handle && entry.uuid.equals(uuid)) {
+                return true;
             }
         }
-        Log.e(TAG, "getServiceHandle() - UUID " + uuid + " not found!");
-        return 0;
-    }
-
-    int getCharacteristicHandle(int serviceHandle, UUID uuid, int instance) {
-        for(Entry entry : mEntries) {
-            if (entry.type == TYPE_CHARACTERISTIC &&
-                entry.serviceHandle == serviceHandle &&
-                entry.instance == instance &&
-                entry.uuid.equals(uuid)) {
-                return entry.handle;
-            }
-        }
-        Log.e(TAG, "getCharacteristicHandle() - Service " + serviceHandle
-                    + ", UUID " + uuid + " not found!");
-        return 0;
+        return false;
     }
 
     void deleteService(int serverIf, int serviceHandle) {
-        for(Iterator <Entry> it = mEntries.iterator(); it.hasNext();) {
+        for (Iterator<Entry> it = mEntries.iterator(); it.hasNext(); ) {
             Entry entry = it.next();
-            if (entry.serverIf != serverIf) continue;
+            if (entry.serverIf != serverIf) {
+                continue;
+            }
 
-            if (entry.handle == serviceHandle ||
-                entry.serviceHandle == serviceHandle)
+            if (entry.handle == serviceHandle || entry.serviceHandle == serviceHandle) {
                 it.remove();
+            }
         }
     }
 
@@ -200,7 +187,7 @@
 
         for (Entry entry : mEntries) {
             sb.append("  " + entry.serverIf + ": [" + entry.handle + "] ");
-            switch(entry.type) {
+            switch (entry.type) {
                 case TYPE_SERVICE:
                     sb.append("Service " + entry.uuid);
                     sb.append(", started " + entry.started);
diff --git a/src/com/android/bluetooth/gatt/PeriodicScanManager.java b/src/com/android/bluetooth/gatt/PeriodicScanManager.java
index 8bcab4e..c39c6f0 100644
--- a/src/com/android/bluetooth/gatt/PeriodicScanManager.java
+++ b/src/com/android/bluetooth/gatt/PeriodicScanManager.java
@@ -16,7 +16,6 @@
 
 package com.android.bluetooth.gatt;
 
-import android.bluetooth.le.AdvertiseData;
 import android.bluetooth.le.IPeriodicAdvertisingCallback;
 import android.bluetooth.le.PeriodicAdvertisingReport;
 import android.bluetooth.le.ScanRecord;
@@ -25,16 +24,12 @@
 import android.os.IInterface;
 import android.os.RemoteException;
 import android.util.Log;
-import com.android.bluetooth.Utils;
+
 import com.android.bluetooth.btservice.AdapterService;
+
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Manages Bluetooth LE Periodic scans
@@ -53,7 +48,9 @@
      * Constructor of {@link SyncManager}.
      */
     PeriodicScanManager(AdapterService adapterService) {
-        if (DBG) Log.d(TAG, "advertise manager created");
+        if (DBG) {
+            Log.d(TAG, "advertise manager created");
+        }
         mAdapterService = adapterService;
     }
 
@@ -62,7 +59,9 @@
     }
 
     void cleanup() {
-        if (DBG) Log.d(TAG, "cleanup()");
+        if (DBG) {
+            Log.d(TAG, "cleanup()");
+        }
         cleanupNative();
         mSyncs.clear();
         sTempRegistrationId = -1;
@@ -88,23 +87,25 @@
     }
 
     class SyncDeathRecipient implements IBinder.DeathRecipient {
-        IPeriodicAdvertisingCallback callback;
+        public IPeriodicAdvertisingCallback callback;
 
-        public SyncDeathRecipient(IPeriodicAdvertisingCallback callback) {
+        SyncDeathRecipient(IPeriodicAdvertisingCallback callback) {
             this.callback = callback;
         }
 
         @Override
         public void binderDied() {
-            if (DBG) Log.d(TAG, "Binder is dead - unregistering advertising set");
+            if (DBG) {
+                Log.d(TAG, "Binder is dead - unregistering advertising set");
+            }
             stopSync(callback);
         }
     }
 
-    Map.Entry<IBinder, SyncInfo> findSync(int sync_handle) {
+    Map.Entry<IBinder, SyncInfo> findSync(int syncHandle) {
         Map.Entry<IBinder, SyncInfo> entry = null;
         for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
-            if (e.getValue().id == sync_handle) {
+            if (e.getValue().id == syncHandle) {
                 entry = e;
                 break;
             }
@@ -112,24 +113,25 @@
         return entry;
     }
 
-    void onSyncStarted(int reg_id, int sync_handle, int sid, int address_type, String address,
-            int phy, int interval, int status) throws Exception {
+    void onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy,
+            int interval, int status) throws Exception {
         if (DBG) {
-            Log.d(TAG, "onSyncStarted() - reg_id=" + reg_id + ", sync_handle=" + sync_handle
-                            + ", status=" + status);
+            Log.d(TAG,
+                    "onSyncStarted() - regId=" + regId + ", syncHandle=" + syncHandle + ", status="
+                            + status);
         }
 
-        Map.Entry<IBinder, SyncInfo> entry = findSync(reg_id);
+        Map.Entry<IBinder, SyncInfo> entry = findSync(regId);
         if (entry == null) {
-            Log.i(TAG, "onSyncStarted() - no callback found for reg_id " + reg_id);
+            Log.i(TAG, "onSyncStarted() - no callback found for regId " + regId);
             // Sync was stopped before it was properly registered.
-            stopSyncNative(sync_handle);
+            stopSyncNative(syncHandle);
             return;
         }
 
         IPeriodicAdvertisingCallback callback = entry.getValue().callback;
         if (status == 0) {
-            entry.setValue(new SyncInfo(sync_handle, entry.getValue().deathRecipient, callback));
+            entry.setValue(new SyncInfo(syncHandle, entry.getValue().deathRecipient, callback));
         } else {
             IBinder binder = entry.getKey();
             binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
@@ -137,41 +139,46 @@
         }
 
         // TODO: fix callback arguments
-        // callback.onSyncStarted(sync_handle, tx_power, status);
+        // callback.onSyncStarted(syncHandle, tx_power, status);
     }
 
-    void onSyncReport(int sync_handle, int tx_power, int rssi, int data_status, byte[] data)
+    void onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data)
             throws Exception {
-        if (DBG) Log.d(TAG, "onSyncReport() - sync_handle=" + sync_handle);
+        if (DBG) {
+            Log.d(TAG, "onSyncReport() - syncHandle=" + syncHandle);
+        }
 
-        Map.Entry<IBinder, SyncInfo> entry = findSync(sync_handle);
+        Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
         if (entry == null) {
-            Log.i(TAG, "onSyncReport() - no callback found for sync_handle " + sync_handle);
+            Log.i(TAG, "onSyncReport() - no callback found for syncHandle " + syncHandle);
             return;
         }
 
         IPeriodicAdvertisingCallback callback = entry.getValue().callback;
-        PeriodicAdvertisingReport report = new PeriodicAdvertisingReport(
-                sync_handle, tx_power, rssi, data_status, ScanRecord.parseFromBytes(data));
+        PeriodicAdvertisingReport report =
+                new PeriodicAdvertisingReport(syncHandle, txPower, rssi, dataStatus,
+                        ScanRecord.parseFromBytes(data));
         callback.onPeriodicAdvertisingReport(report);
     }
 
-    void onSyncLost(int sync_handle) throws Exception {
-        if (DBG) Log.d(TAG, "onSyncLost() - sync_handle=" + sync_handle);
+    void onSyncLost(int syncHandle) throws Exception {
+        if (DBG) {
+            Log.d(TAG, "onSyncLost() - syncHandle=" + syncHandle);
+        }
 
-        Map.Entry<IBinder, SyncInfo> entry = findSync(sync_handle);
+        Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
         if (entry == null) {
-            Log.i(TAG, "onSyncLost() - no callback found for sync_handle " + sync_handle);
+            Log.i(TAG, "onSyncLost() - no callback found for syncHandle " + syncHandle);
             return;
         }
 
         IPeriodicAdvertisingCallback callback = entry.getValue().callback;
         mSyncs.remove(entry);
-        callback.onSyncLost(sync_handle);
+        callback.onSyncLost(syncHandle);
     }
 
-    void startSync(
-            ScanResult scanResult, int skip, int timeout, IPeriodicAdvertisingCallback callback) {
+    void startSync(ScanResult scanResult, int skip, int timeout,
+            IPeriodicAdvertisingCallback callback) {
         SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback);
         IBinder binder = toBinder(callback);
         try {
@@ -183,16 +190,20 @@
         String address = scanResult.getDevice().getAddress();
         int sid = scanResult.getAdvertisingSid();
 
-        int cb_id = --sTempRegistrationId;
-        mSyncs.put(binder, new SyncInfo(cb_id, deathRecipient, callback));
+        int cbId = --sTempRegistrationId;
+        mSyncs.put(binder, new SyncInfo(cbId, deathRecipient, callback));
 
-        if (DBG) Log.d(TAG, "startSync() - reg_id=" + cb_id + ", callback: " + binder);
-        startSyncNative(sid, address, skip, timeout, cb_id);
+        if (DBG) {
+            Log.d(TAG, "startSync() - reg_id=" + cbId + ", callback: " + binder);
+        }
+        startSyncNative(sid, address, skip, timeout, cbId);
     }
 
     void stopSync(IPeriodicAdvertisingCallback callback) {
         IBinder binder = toBinder(callback);
-        if (DBG) Log.d(TAG, "stopSync() " + binder);
+        if (DBG) {
+            Log.d(TAG, "stopSync() " + binder);
+        }
 
         SyncInfo sync = mSyncs.remove(binder);
         if (sync == null) {
@@ -200,25 +211,29 @@
             return;
         }
 
-        Integer sync_handle = sync.id;
+        Integer syncHandle = sync.id;
         binder.unlinkToDeath(sync.deathRecipient, 0);
 
-        if (sync_handle < 0) {
+        if (syncHandle < 0) {
             Log.i(TAG, "stopSync() - not finished registration yet");
             // Sync will be freed once initiated in onSyncStarted()
             return;
         }
 
-        stopSyncNative(sync_handle);
+        stopSyncNative(syncHandle);
     }
 
     static {
         classInitNative();
     }
 
-    private native static void classInitNative();
+    private static native void classInitNative();
+
     private native void initializeNative();
+
     private native void cleanupNative();
-    private native void startSyncNative(int sid, String address, int skip, int timeout, int reg_id);
-    private native void stopSyncNative(int sync_handle);
+
+    private native void startSyncNative(int sid, String address, int skip, int timeout, int regId);
+
+    private native void stopSyncNative(int syncHandle);
 }
diff --git a/src/com/android/bluetooth/gatt/ScanClient.java b/src/com/android/bluetooth/gatt/ScanClient.java
index f44ce2d..f774347 100644
--- a/src/com/android/bluetooth/gatt/ScanClient.java
+++ b/src/com/android/bluetooth/gatt/ScanClient.java
@@ -19,6 +19,7 @@
 import android.bluetooth.le.ResultStorageDescriptor;
 import android.bluetooth.le.ScanFilter;
 import android.bluetooth.le.ScanSettings;
+import android.os.Binder;
 
 import java.util.List;
 import java.util.Objects;
@@ -30,22 +31,24 @@
  * @hide
  */
 /* package */class ScanClient {
-    int scannerId;
-    UUID[] uuids;
-    ScanSettings settings;
-    List<ScanFilter> filters;
-    List<List<ResultStorageDescriptor>> storages;
+    public int scannerId;
+    public UUID[] uuids;
+    public ScanSettings settings;
+    public ScanSettings passiveSettings;
+    public int appUid;
+    public List<ScanFilter> filters;
+    public List<List<ResultStorageDescriptor>> storages;
     // App associated with the scan client died.
-    boolean appDied;
-    boolean hasLocationPermission;
-    boolean hasPeersMacAddressPermission;
+    public boolean appDied;
+    public boolean hasLocationPermission;
+    public boolean hasPeersMacAddressPermission;
     // Pre-M apps are allowed to get scan results even if location is disabled
-    boolean legacyForegroundApp;
+    public boolean legacyForegroundApp;
 
-    AppScanStats stats = null;
+    public AppScanStats stats = null;
 
-    private static final ScanSettings DEFAULT_SCAN_SETTINGS = new ScanSettings.Builder()
-            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+    private static final ScanSettings DEFAULT_SCAN_SETTINGS =
+            new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
 
     ScanClient(int scannerId) {
         this(scannerId, new UUID[0], DEFAULT_SCAN_SETTINGS, null, null);
@@ -55,8 +58,7 @@
         this(scannerId, uuids, DEFAULT_SCAN_SETTINGS, null, null);
     }
 
-    ScanClient(int scannerId, ScanSettings settings,
-            List<ScanFilter> filters) {
+    ScanClient(int scannerId, ScanSettings settings, List<ScanFilter> filters) {
         this(scannerId, new UUID[0], settings, filters, null);
     }
 
@@ -70,8 +72,10 @@
         this.scannerId = scannerId;
         this.uuids = uuids;
         this.settings = settings;
+        this.passiveSettings = null;
         this.filters = filters;
         this.storages = storages;
+        this.appUid = Binder.getCallingUid();
     }
 
     @Override
diff --git a/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
index 3dab4af..15233e4 100644
--- a/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -23,7 +23,6 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 
@@ -48,9 +47,9 @@
     private static final byte DEVICE_TYPE_ALL = 2;
 
     class Entry {
+        public byte type;
         public String address;
         public byte addr_type;
-        public byte type;
         public UUID uuid;
         public UUID uuid_mask;
         public String name;
@@ -58,33 +57,6 @@
         public int company_mask;
         public byte[] data;
         public byte[] data_mask;
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(address, addr_type, type, uuid, uuid_mask,
-                                name, company, company_mask,
-                                Arrays.hashCode(data),
-                                Arrays.hashCode(data_mask));
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null || getClass() != obj.getClass()) {
-                return false;
-            }
-            Entry other = (Entry) obj;
-            return Objects.equals(address, other.address) &&
-                    addr_type == other.addr_type && type == other.type &&
-                    Objects.equals(uuid, other.uuid) &&
-                    Objects.equals(uuid_mask, other.uuid_mask) &&
-                    Objects.equals(name, other.name) &&
-                            company == other.company && company_mask == other.company_mask &&
-                    Objects.deepEquals(data, other.data) &&
-                    Objects.deepEquals(data_mask, other.data_mask);
-        }
     }
 
     private Set<Entry> mEntries = new HashSet<Entry>();
@@ -111,11 +83,11 @@
         mEntries.add(entry);
     }
 
-    void addUuid(UUID uuid, UUID uuid_mask) {
+    void addUuid(UUID uuid, UUID uuidMask) {
         Entry entry = new Entry();
         entry.type = TYPE_SERVICE_UUID;
         entry.uuid = uuid;
-        entry.uuid_mask = uuid_mask;
+        entry.uuid_mask = uuidMask;
         mEntries.add(entry);
     }
 
@@ -144,13 +116,13 @@
         mEntries.add(entry);
     }
 
-    void addManufacturerData(int company, int company_mask, byte[] data, byte[] data_mask) {
+    void addManufacturerData(int company, int companyMask, byte[] data, byte[] dataMask) {
         Entry entry = new Entry();
         entry.type = TYPE_MANUFACTURER_DATA;
         entry.company = company;
-        entry.company_mask = company_mask;
+        entry.company_mask = companyMask;
         entry.data = data;
-        entry.data_mask = data_mask;
+        entry.data_mask = dataMask;
         mEntries.add(entry);
     }
 
@@ -163,7 +135,7 @@
     }
 
     Entry pop() {
-        if (isEmpty()) {
+        if (mEntries.isEmpty()) {
             return null;
         }
         Iterator<Entry> iterator = mEntries.iterator();
@@ -172,22 +144,6 @@
         return entry;
     }
 
-    boolean isEmpty() {
-        return mEntries.isEmpty();
-    }
-
-    void clearUuids() {
-        for (Iterator<Entry> it = mEntries.iterator(); it.hasNext();) {
-            Entry entry = it.next();
-            if (entry.type == TYPE_SERVICE_UUID)
-                it.remove();
-        }
-    }
-
-    void clear() {
-        mEntries.clear();
-    }
-
     /**
      * Compute feature selection based on the filters presented.
      */
@@ -199,12 +155,17 @@
         return selc;
     }
 
+    ScanFilterQueue.Entry[] toArray() {
+        return mEntries.toArray(new ScanFilterQueue.Entry[mEntries.size()]);
+    }
+
     /**
      * Add ScanFilter to scan filter queue.
      */
     void addScanFilter(ScanFilter filter) {
-        if (filter == null)
+        if (filter == null) {
             return;
+        }
         if (filter.getDeviceName() != null) {
             addName(filter.getDeviceName());
         }
@@ -215,8 +176,7 @@
             if (filter.getServiceUuidMask() == null) {
                 addUuid(filter.getServiceUuid().getUuid());
             } else {
-                addUuid(filter.getServiceUuid().getUuid(),
-                        filter.getServiceUuidMask().getUuid());
+                addUuid(filter.getServiceUuid().getUuid(), filter.getServiceUuidMask().getUuid());
             }
         }
         if (filter.getManufacturerData() != null) {
@@ -244,20 +204,17 @@
     }
 
     private byte[] concate(ParcelUuid serviceDataUuid, byte[] serviceData) {
-        int dataLen = 2 + (serviceData == null ? 0 : serviceData.length);
+        byte[] uuid = BluetoothUuid.uuidToBytes(serviceDataUuid);
+
+        int dataLen = uuid.length + (serviceData == null ? 0 : serviceData.length);
         // If data is too long, don't add it to hardware scan filter.
         if (dataLen > MAX_LEN_PER_FIELD) {
             return null;
         }
         byte[] concated = new byte[dataLen];
-        // Extract 16 bit UUID value.
-        int uuidValue = BluetoothUuid.getServiceIdentifierFromParcelUuid(
-                serviceDataUuid);
-        // First two bytes are service data UUID in little-endian.
-        concated[0] = (byte) (uuidValue & 0xFF);
-        concated[1] = (byte) ((uuidValue >> 8) & 0xFF);
+        System.arraycopy(uuid, 0, concated, 0, uuid.length);
         if (serviceData != null) {
-            System.arraycopy(serviceData, 0, concated, 2, serviceData.length);
+            System.arraycopy(serviceData, 0, concated, uuid.length, serviceData.length);
         }
         return concated;
     }
diff --git a/src/com/android/bluetooth/gatt/ScanManager.java b/src/com/android/bluetooth/gatt/ScanManager.java
index 65f4f6d..5207c69 100644
--- a/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/src/com/android/bluetooth/gatt/ScanManager.java
@@ -16,6 +16,7 @@
 
 package com.android.bluetooth.gatt;
 
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
@@ -23,17 +24,19 @@
 import android.bluetooth.le.ScanFilter;
 import android.bluetooth.le.ScanSettings;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.display.DisplayManager;
+import android.location.LocationManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.Display;
 
@@ -73,6 +76,7 @@
     private static final int MSG_SCAN_TIMEOUT = 3;
     private static final int MSG_SUSPEND_SCANS = 4;
     private static final int MSG_RESUME_SCANS = 5;
+    private static final int MSG_IMPORTANCE_CHANGE = 6;
     private static final String ACTION_REFRESH_BATCHED_SCAN =
             "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";
 
@@ -83,12 +87,12 @@
     // Scan parameters for batch scan.
     private BatchScanParams mBatchScanParms;
 
-    private Integer curUsedTrackableAdvertisements;
+    private Integer mCurUsedTrackableAdvertisements;
     private GattService mService;
     private BroadcastReceiver mBatchAlarmReceiver;
     private boolean mBatchAlarmReceiverRegistered;
     private ScanNative mScanNative;
-    private ClientHandler mHandler;
+    private volatile ClientHandler mHandler;
 
     private Set<ScanClient> mRegularScanClients;
     private Set<ScanClient> mBatchClients;
@@ -98,15 +102,33 @@
 
     private DisplayManager mDm;
 
+    private ActivityManager mActivityManager;
+    private LocationManager mLocationManager;
+    private static final int FOREGROUND_IMPORTANCE_CUTOFF =
+            ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+
+    private class UidImportance {
+        public int uid;
+        public int importance;
+
+        UidImportance(int uid, int importance) {
+            this.uid = uid;
+            this.importance = importance;
+        }
+    }
+
     ScanManager(GattService service) {
-        mRegularScanClients = Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>());
+        mRegularScanClients =
+                Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>());
         mBatchClients = Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>());
         mSuspendedScanClients =
                 Collections.newSetFromMap(new ConcurrentHashMap<ScanClient, Boolean>());
         mService = service;
         mScanNative = new ScanNative();
-        curUsedTrackableAdvertisements = 0;
+        mCurUsedTrackableAdvertisements = 0;
         mDm = (DisplayManager) mService.getSystemService(Context.DISPLAY_SERVICE);
+        mActivityManager = (ActivityManager) mService.getSystemService(Context.ACTIVITY_SERVICE);
+        mLocationManager = (LocationManager) mService.getSystemService(Context.LOCATION_SERVICE);
     }
 
     void start() {
@@ -116,6 +138,12 @@
         if (mDm != null) {
             mDm.registerDisplayListener(mDisplayListener, null);
         }
+        if (mActivityManager != null) {
+            mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
+                    FOREGROUND_IMPORTANCE_CUTOFF);
+        }
+        IntentFilter locationIntentFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
+        mService.registerReceiver(mLocationReceiver, locationIntentFilter);
     }
 
     void cleanup() {
@@ -124,6 +152,14 @@
         mSuspendedScanClients.clear();
         mScanNative.cleanup();
 
+        if (mActivityManager != null) {
+            try {
+                mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG, "exception when invoking removeOnUidImportanceListener", e);
+            }
+        }
+
         if (mDm != null) {
             mDm.unregisterDisplayListener(mDisplayListener);
         }
@@ -133,15 +169,21 @@
             mHandler.removeCallbacksAndMessages(null);
             Looper looper = mHandler.getLooper();
             if (looper != null) {
-                looper.quit();
+                looper.quitSafely();
             }
             mHandler = null;
         }
+
+        try {
+            mService.unregisterReceiver(mLocationReceiver);
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "exception when invoking unregisterReceiver(mLocationReceiver)", e);
+        }
     }
 
     void registerScanner(UUID uuid) {
-        mScanNative.registerScannerNative(
-            uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
+        mScanNative.registerScannerNative(uuid.getLeastSignificantBits(),
+                uuid.getMostSignificantBits());
     }
 
     void unregisterScanner(int scannerId) {
@@ -190,7 +232,9 @@
     }
 
     void callbackDone(int scannerId, int status) {
-        if (DBG) Log.d(TAG, "callback done for scannerId - " + scannerId + " status - " + status);
+        if (DBG) {
+            Log.d(TAG, "callback done for scannerId - " + scannerId + " status - " + status);
+        }
         if (status == 0) {
             mLatch.countDown();
         }
@@ -198,10 +242,15 @@
     }
 
     private void sendMessage(int what, ScanClient client) {
+        final ClientHandler handler = mHandler;
+        if (handler == null) {
+            Log.d(TAG, "sendMessage: mHandler is null.");
+            return;
+        }
         Message message = new Message();
         message.what = what;
         message.obj = client;
-        mHandler.sendMessage(message);
+        handler.sendMessage(message);
     }
 
     private boolean isFilteringSupported() {
@@ -218,19 +267,18 @@
 
         @Override
         public void handleMessage(Message msg) {
-            ScanClient client = (ScanClient) msg.obj;
             switch (msg.what) {
                 case MSG_START_BLE_SCAN:
-                    handleStartScan(client);
+                    handleStartScan((ScanClient) msg.obj);
                     break;
                 case MSG_STOP_BLE_SCAN:
-                    handleStopScan(client);
+                    handleStopScan((ScanClient) msg.obj);
                     break;
                 case MSG_FLUSH_BATCH_RESULTS:
-                    handleFlushBatchResults(client);
+                    handleFlushBatchResults((ScanClient) msg.obj);
                     break;
                 case MSG_SCAN_TIMEOUT:
-                    mScanNative.regularScanTimeout(client);
+                    mScanNative.regularScanTimeout((ScanClient) msg.obj);
                     break;
                 case MSG_SUSPEND_SCANS:
                     handleSuspendScans();
@@ -238,6 +286,9 @@
                 case MSG_RESUME_SCANS:
                     handleResumeScans();
                     break;
+                case MSG_IMPORTANCE_CHANGE:
+                    handleImportanceChange((UidImportance) msg.obj);
+                    break;
                 default:
                     // Shouldn't happen.
                     Log.e(TAG, "received an unkown message : " + msg.what);
@@ -247,7 +298,9 @@
         void handleStartScan(ScanClient client) {
             Utils.enforceAdminPermission(mService);
             boolean isFiltered = (client.filters != null) && !client.filters.isEmpty();
-            if (DBG) Log.d(TAG, "handling starting scan");
+            if (DBG) {
+                Log.d(TAG, "handling starting scan");
+            }
 
             if (!isScanSupported(client)) {
                 Log.e(TAG, "Scan settings not supported");
@@ -260,10 +313,23 @@
             }
 
             if (!mScanNative.isOpportunisticScanClient(client) && !isScreenOn() && !isFiltered) {
-                Log.e(TAG,
-                        "Cannot start unfiltered scan in screen-off. This scan will be resumed later: "
-                                + client.scannerId);
+                Log.w(TAG, "Cannot start unfiltered scan in screen-off. This scan will be resumed "
+                        + "later: " + client.scannerId);
                 mSuspendedScanClients.add(client);
+                if (client.stats != null) {
+                    client.stats.recordScanSuspend(client.scannerId);
+                }
+                return;
+            }
+
+            final boolean locationEnabled = mLocationManager.isLocationEnabled();
+            if (!locationEnabled && !isFiltered && !client.legacyForegroundApp) {
+                Log.i(TAG, "Cannot start unfiltered scan in location-off. This scan will be"
+                        + " resumed when location is on: " + client.scannerId);
+                mSuspendedScanClients.add(client);
+                if (client.stats != null) {
+                    client.stats.recordScanSuspend(client.scannerId);
+                }
                 return;
             }
 
@@ -278,10 +344,10 @@
                     mScanNative.configureRegularScanParams();
 
                     if (!mScanNative.isExemptFromScanDowngrade(client)) {
-                        Message msg = mHandler.obtainMessage(MSG_SCAN_TIMEOUT);
+                        Message msg = obtainMessage(MSG_SCAN_TIMEOUT);
                         msg.obj = client;
                         // Only one timeout message should exist at any time
-                        mHandler.sendMessageDelayed(msg, AppScanStats.SCAN_TIMEOUT_MS);
+                        sendMessageDelayed(msg, AppScanStats.SCAN_TIMEOUT_MS);
                     }
                 }
             }
@@ -289,7 +355,9 @@
 
         void handleStopScan(ScanClient client) {
             Utils.enforceAdminPermission(mService);
-            if (client == null) return;
+            if (client == null) {
+                return;
+            }
 
             if (mSuspendedScanClients.contains(client)) {
                 mSuspendedScanClients.remove(client);
@@ -299,7 +367,7 @@
                 mScanNative.stopRegularScan(client);
 
                 if (mScanNative.numRegularScanClients() == 0) {
-                    mHandler.removeMessages(MSG_SCAN_TIMEOUT);
+                    removeMessages(MSG_SCAN_TIMEOUT);
                 }
 
                 if (!mScanNative.isOpportunisticScanClient(client)) {
@@ -309,7 +377,9 @@
                 mScanNative.stopBatchScan(client);
             }
             if (client.appDied) {
-                if (DBG) Log.d(TAG, "app died, unregister scanner - " + client.scannerId);
+                if (DBG) {
+                    Log.d(TAG, "app died, unregister scanner - " + client.scannerId);
+                }
                 mService.unregisterScanner(client.scannerId);
             }
         }
@@ -327,8 +397,8 @@
                 return false;
             }
             ScanSettings settings = client.settings;
-            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES &&
-                    settings.getReportDelayMillis() != 0;
+            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
+                    && settings.getReportDelayMillis() != 0;
         }
 
         private boolean isScanSupported(ScanClient client) {
@@ -339,14 +409,14 @@
             if (isFilteringSupported()) {
                 return true;
             }
-            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES &&
-                    settings.getReportDelayMillis() == 0;
+            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
+                    && settings.getReportDelayMillis() == 0;
         }
 
         void handleSuspendScans() {
             for (ScanClient client : mRegularScanClients) {
-                if (!mScanNative.isOpportunisticScanClient(client)
-                        && (client.filters == null || client.filters.isEmpty())) {
+                if (!mScanNative.isOpportunisticScanClient(client) && (client.filters == null
+                        || client.filters.isEmpty()) && !client.legacyForegroundApp) {
                     /*Suspend unfiltered scans*/
                     if (client.stats != null) {
                         client.stats.recordScanSuspend(client.scannerId);
@@ -372,9 +442,9 @@
      * Parameters for batch scans.
      */
     class BatchScanParams {
-        int scanMode;
-        int fullScanscannerId;
-        int truncatedScanscannerId;
+        public int scanMode;
+        public int fullScanscannerId;
+        public int truncatedScanscannerId;
 
         BatchScanParams() {
             scanMode = -1;
@@ -398,7 +468,7 @@
     }
 
     public int getCurrentUsedTrackingAdvertisement() {
-        return curUsedTrackableAdvertisements;
+        return mCurUsedTrackableAdvertisements;
     }
 
     private class ScanNative {
@@ -420,12 +490,12 @@
         /**
          * Scan params corresponding to regular scan setting
          */
-        private static final int SCAN_MODE_LOW_POWER_WINDOW_MS = 500;
-        private static final int SCAN_MODE_LOW_POWER_INTERVAL_MS = 5000;
-        private static final int SCAN_MODE_BALANCED_WINDOW_MS = 2000;
-        private static final int SCAN_MODE_BALANCED_INTERVAL_MS = 5000;
-        private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 5000;
-        private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 5000;
+        private static final int SCAN_MODE_LOW_POWER_WINDOW_MS = 512;
+        private static final int SCAN_MODE_LOW_POWER_INTERVAL_MS = 5120;
+        private static final int SCAN_MODE_BALANCED_WINDOW_MS = 1024;
+        private static final int SCAN_MODE_BALANCED_INTERVAL_MS = 4096;
+        private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 4096;
+        private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 4096;
 
         /**
          * Onfound/onlost for scan settings
@@ -469,7 +539,7 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
             mBatchAlarmReceiver = new BroadcastReceiver() {
-                    @Override
+                @Override
                 public void onReceive(Context context, Intent intent) {
                     Log.d(TAG, "awakened up at time " + SystemClock.elapsedRealtime());
                     String action = intent.getAction();
@@ -479,7 +549,9 @@
                             return;
                         }
                         // Note this actually flushes all pending batch data.
-                        flushBatchScanResults(mBatchClients.iterator().next());
+                        if (mBatchClients.iterator().hasNext()) {
+                            flushBatchScanResults(mBatchClients.iterator().next());
+                        }
                     }
                 }
             };
@@ -512,11 +584,11 @@
 
             if (DBG) {
                 Log.d(TAG, "configureRegularScanParams() - ScanSetting Scan mode=" + curScanSetting
-                                + " mLastConfiguredScanSetting=" + mLastConfiguredScanSetting);
+                        + " mLastConfiguredScanSetting=" + mLastConfiguredScanSetting);
             }
 
-            if (curScanSetting != Integer.MIN_VALUE &&
-                    curScanSetting != ScanSettings.SCAN_MODE_OPPORTUNISTIC) {
+            if (curScanSetting != Integer.MIN_VALUE
+                    && curScanSetting != ScanSettings.SCAN_MODE_OPPORTUNISTIC) {
                 if (curScanSetting != mLastConfiguredScanSetting) {
                     int scanWindow = getScanWindowMillis(client.settings);
                     int scanInterval = getScanIntervalMillis(client.settings);
@@ -526,8 +598,7 @@
                     gattClientScanNative(false);
                     if (DBG) {
                         Log.d(TAG, "configureRegularScanParams - scanInterval = " + scanInterval
-                                        + "configureRegularScanParams - scanWindow = "
-                                        + scanWindow);
+                                + "configureRegularScanParams - scanWindow = " + scanWindow);
                     }
                     gattSetScanParametersNative(client.scannerId, scanInterval, scanWindow);
                     gattClientScanNative(true);
@@ -535,7 +606,9 @@
                 }
             } else {
                 mLastConfiguredScanSetting = curScanSetting;
-                if (DBG) Log.d(TAG, "configureRegularScanParams() - queue emtpy, scan stopped");
+                if (DBG) {
+                    Log.d(TAG, "configureRegularScanParams() - queue emtpy, scan stopped");
+                }
             }
         }
 
@@ -569,7 +642,7 @@
 
         private int numRegularScanClients() {
             int num = 0;
-            for (ScanClient client: mRegularScanClients) {
+            for (ScanClient client : mRegularScanClients) {
                 if (client.settings.getScanMode() != ScanSettings.SCAN_MODE_OPPORTUNISTIC) {
                     num++;
                 }
@@ -583,15 +656,15 @@
             }
             configureScanFilters(client);
             if (!isOpportunisticScanClient(client)) {
-                // Reset batch scan. May need to stop the existing batch scan and update scan params.
+                // Reset batch scan. May need to stop the existing batch scan and update scan
+                // params.
                 resetBatchScan(client);
             }
         }
 
         private boolean isExemptFromScanDowngrade(ScanClient client) {
-          return isOpportunisticScanClient(client)
-              || isFirstMatchScanClient(client)
-              || !shouldUseAllPassFilter(client);
+            return isOpportunisticScanClient(client) || isFirstMatchScanClient(client)
+                    || !shouldUseAllPassFilter(client);
         }
 
         private boolean isOpportunisticScanClient(ScanClient client) {
@@ -599,7 +672,8 @@
         }
 
         private boolean isFirstMatchScanClient(ScanClient client) {
-            return (client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
+            return (client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
+                    != 0;
         }
 
         private void resetBatchScan(ScanClient client) {
@@ -607,7 +681,9 @@
             BatchScanParams batchScanParams = getBatchScanParams();
             // Stop batch if batch scan params changed and previous params is not null.
             if (mBatchScanParms != null && (!mBatchScanParms.equals(batchScanParams))) {
-                if (DBG) Log.d(TAG, "stopping BLe Batch");
+                if (DBG) {
+                    Log.d(TAG, "stopping BLe Batch");
+                }
                 resetCountDownLatch();
                 gattClientStopBatchScanNative(scannerId);
                 waitForCallback();
@@ -618,11 +694,15 @@
             // Start batch if batchScanParams changed and current params is not null.
             if (batchScanParams != null && (!batchScanParams.equals(mBatchScanParms))) {
                 int notifyThreshold = 95;
-                if (DBG) Log.d(TAG, "Starting BLE batch scan");
+                if (DBG) {
+                    Log.d(TAG, "Starting BLE batch scan");
+                }
                 int resultType = getResultType(batchScanParams);
                 int fullScanPercent = getFullScanStoragePercent(resultType);
                 resetCountDownLatch();
-                if (DBG) Log.d(TAG, "configuring batch scan storage, appIf " + client.scannerId);
+                if (DBG) {
+                    Log.d(TAG, "configuring batch scan storage, appIf " + client.scannerId);
+                }
                 gattClientConfigBatchScanStorageNative(client.scannerId, fullScanPercent,
                         100 - fullScanPercent, notifyThreshold);
                 waitForCallback();
@@ -631,8 +711,8 @@
                         Utils.millsToUnit(getBatchScanIntervalMillis(batchScanParams.scanMode));
                 int scanWindow =
                         Utils.millsToUnit(getBatchScanWindowMillis(batchScanParams.scanMode));
-                gattClientStartBatchScanNative(scannerId, resultType, scanInterval,
-                        scanWindow, 0, DISCARD_OLDEST_WHEN_BUFFER_FULL);
+                gattClientStartBatchScanNative(scannerId, resultType, scanInterval, scanWindow, 0,
+                        DISCARD_OLDEST_WHEN_BUFFER_FULL);
                 waitForCallback();
             }
             mBatchScanParms = batchScanParams;
@@ -709,24 +789,25 @@
             // [batchTriggerIntervalMillis, 1.1 * batchTriggerIntervalMillis]
             long windowLengthMillis = batchTriggerIntervalMillis / 10;
             long windowStartMillis = SystemClock.elapsedRealtime() + batchTriggerIntervalMillis;
-            mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                    windowStartMillis, windowLengthMillis,
-                    mBatchScanIntervalIntent);
+            mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, windowStartMillis,
+                    windowLengthMillis, mBatchScanIntervalIntent);
         }
 
         void stopRegularScan(ScanClient client) {
             // Remove scan filters and recycle filter indices.
-            if (client == null) return;
+            if (client == null) {
+                return;
+            }
             int deliveryMode = getDeliveryMode(client);
             if (deliveryMode == DELIVERY_MODE_ON_FOUND_LOST) {
                 for (ScanFilter filter : client.filters) {
                     int entriesToFree = getNumOfTrackingAdvertisements(client.settings);
                     if (!manageAllocationOfTrackingAdvertisement(entriesToFree, false)) {
                         Log.e(TAG, "Error freeing for onfound/onlost filter resources "
-                                    + entriesToFree);
+                                + entriesToFree);
                         try {
                             mService.onScanManagerErrorCallback(client.scannerId,
-                                            ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
+                                    ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
                         } catch (RemoteException e) {
                             Log.e(TAG, "failed on onScanManagerCallback at freeing", e);
                         }
@@ -735,7 +816,9 @@
             }
             mRegularScanClients.remove(client);
             if (numRegularScanClients() == 0) {
-                if (DBG) Log.d(TAG, "stop scan");
+                if (DBG) {
+                    Log.d(TAG, "stop scan");
+                }
                 gattClientScanNative(false);
             }
             removeScanFilters(client.scannerId);
@@ -753,7 +836,9 @@
             // The scan should continue for background scans
             configureRegularScanParams();
             if (numRegularScanClients() == 0) {
-                if (DBG) Log.d(TAG, "stop scan");
+                if (DBG) {
+                    Log.d(TAG, "stop scan");
+                }
                 gattClientScanNative(false);
             }
         }
@@ -774,7 +859,9 @@
         // Find the regular scan client information.
         ScanClient getRegularScanClient(int scannerId) {
             for (ScanClient client : mRegularScanClients) {
-              if (client.scannerId == scannerId) return client;
+                if (client.scannerId == scannerId) {
+                    return client;
+                }
             }
             return null;
         }
@@ -788,7 +875,9 @@
         }
 
         void flushBatchResults(int scannerId) {
-            if (DBG) Log.d(TAG, "flushPendingBatchResults - scannerId = " + scannerId);
+            if (DBG) {
+                Log.d(TAG, "flushPendingBatchResults - scannerId = " + scannerId);
+            }
             if (mBatchScanParms.fullScanscannerId != -1) {
                 resetCountDownLatch();
                 gattClientReadScanReportsNative(mBatchScanParms.fullScanscannerId,
@@ -817,8 +906,8 @@
             long intervalMillis = Long.MAX_VALUE;
             for (ScanClient client : mBatchClients) {
                 if (client.settings != null && client.settings.getReportDelayMillis() > 0) {
-                    intervalMillis = Math.min(intervalMillis,
-                            client.settings.getReportDelayMillis());
+                    intervalMillis =
+                            Math.min(intervalMillis, client.settings.getReportDelayMillis());
                 }
             }
             return intervalMillis;
@@ -846,12 +935,13 @@
             waitForCallback();
 
             if (shouldUseAllPassFilter(client)) {
-                int filterIndex = (deliveryMode == DELIVERY_MODE_BATCH) ?
-                        ALL_PASS_FILTER_INDEX_BATCH_SCAN : ALL_PASS_FILTER_INDEX_REGULAR_SCAN;
+                int filterIndex =
+                        (deliveryMode == DELIVERY_MODE_BATCH) ? ALL_PASS_FILTER_INDEX_BATCH_SCAN
+                                : ALL_PASS_FILTER_INDEX_REGULAR_SCAN;
                 resetCountDownLatch();
                 // Don't allow Onfound/onlost with all pass
-                configureFilterParamter(scannerId, client, ALL_PASS_FILTER_SELECTION,
-                                filterIndex, 0);
+                configureFilterParamter(scannerId, client, ALL_PASS_FILTER_SELECTION, filterIndex,
+                        0);
                 waitForCallback();
             } else {
                 Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>();
@@ -860,27 +950,27 @@
                     queue.addScanFilter(filter);
                     int featureSelection = queue.getFeatureSelection();
                     int filterIndex = mFilterIndexStack.pop();
-                    while (!queue.isEmpty()) {
-                        resetCountDownLatch();
-                        addFilterToController(scannerId, queue.pop(), filterIndex);
-                        waitForCallback();
-                    }
+
+                    resetCountDownLatch();
+                    gattClientScanFilterAddNative(scannerId, queue.toArray(), filterIndex);
+                    waitForCallback();
+
                     resetCountDownLatch();
                     if (deliveryMode == DELIVERY_MODE_ON_FOUND_LOST) {
                         trackEntries = getNumOfTrackingAdvertisements(client.settings);
                         if (!manageAllocationOfTrackingAdvertisement(trackEntries, true)) {
-                            Log.e(TAG, "No hardware resources for onfound/onlost filter " +
-                                    trackEntries);
+                            Log.e(TAG, "No hardware resources for onfound/onlost filter "
+                                    + trackEntries);
                             try {
                                 mService.onScanManagerErrorCallback(scannerId,
-                                            ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
+                                        ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
                             } catch (RemoteException e) {
                                 Log.e(TAG, "failed on onScanManagerCallback", e);
                             }
                         }
                     }
                     configureFilterParamter(scannerId, client, featureSelection, filterIndex,
-                                            trackEntries);
+                            trackEntries);
                     waitForCallback();
                     clientFilterIndices.add(filterIndex);
                 }
@@ -971,51 +1061,6 @@
             return client.filters.size() > mFilterIndexStack.size();
         }
 
-        private void addFilterToController(int scannerId, ScanFilterQueue.Entry entry,
-                int filterIndex) {
-            if (DBG) Log.d(TAG, "addFilterToController: " + entry.type);
-            switch (entry.type) {
-                case ScanFilterQueue.TYPE_DEVICE_ADDRESS:
-                    if (DBG) Log.d(TAG, "add address " + entry.address);
-                    gattClientScanFilterAddNative(scannerId, entry.type, filterIndex, 0, 0, 0, 0, 0,
-                            0,
-                            "", entry.address, (byte) entry.addr_type, new byte[0], new byte[0]);
-                    break;
-
-                case ScanFilterQueue.TYPE_SERVICE_DATA:
-                    gattClientScanFilterAddNative(scannerId, entry.type, filterIndex, 0, 0, 0, 0, 0,
-                            0,
-                            "", "", (byte) 0, entry.data, entry.data_mask);
-                    break;
-
-                case ScanFilterQueue.TYPE_SERVICE_UUID:
-                case ScanFilterQueue.TYPE_SOLICIT_UUID:
-                    gattClientScanFilterAddNative(scannerId, entry.type, filterIndex, 0, 0,
-                            entry.uuid.getLeastSignificantBits(),
-                            entry.uuid.getMostSignificantBits(),
-                            entry.uuid_mask.getLeastSignificantBits(),
-                            entry.uuid_mask.getMostSignificantBits(),
-                            "", "", (byte) 0, new byte[0], new byte[0]);
-                    break;
-
-                case ScanFilterQueue.TYPE_LOCAL_NAME:
-                    if (DBG) Log.d(TAG, "adding filters: " + entry.name);
-                    gattClientScanFilterAddNative(scannerId, entry.type, filterIndex, 0, 0, 0, 0, 0,
-                            0,
-                            entry.name, "", (byte) 0, new byte[0], new byte[0]);
-                    break;
-
-                case ScanFilterQueue.TYPE_MANUFACTURER_DATA:
-                    int len = entry.data.length;
-                    if (entry.data_mask.length != len)
-                        return;
-                    gattClientScanFilterAddNative(scannerId, entry.type, filterIndex, entry.company,
-                            entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0,
-                            entry.data, entry.data_mask);
-                    break;
-            }
-        }
-
         private void initFilterIndexStack() {
             int maxFiltersSupported =
                     AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported();
@@ -1040,12 +1085,13 @@
             onLostTimeout = 10000;
             if (DBG) {
                 Log.d(TAG, "configureFilterParamter " + onFoundTimeout + " " + onLostTimeout + " "
-                                + onFoundCount + " " + numOfTrackingEntries);
+                        + onFoundCount + " " + numOfTrackingEntries);
             }
-            FilterParams FiltValue = new FilterParams(scannerId, filterIndex, featureSelection,
-                    LIST_LOGIC_TYPE, FILTER_LOGIC_TYPE, rssiThreshold, rssiThreshold, deliveryMode,
-                    onFoundTimeout, onLostTimeout, onFoundCount, numOfTrackingEntries);
-            gattClientScanFilterParamAddNative(FiltValue);
+            FilterParams filtValue =
+                    new FilterParams(scannerId, filterIndex, featureSelection, LIST_LOGIC_TYPE,
+                            FILTER_LOGIC_TYPE, rssiThreshold, rssiThreshold, deliveryMode,
+                            onFoundTimeout, onLostTimeout, onFoundCount, numOfTrackingEntries);
+            gattClientScanFilterParamAddNative(filtValue);
         }
 
         // Get delivery mode based on scan settings.
@@ -1066,33 +1112,67 @@
         }
 
         private int getScanWindowMillis(ScanSettings settings) {
+            ContentResolver resolver = mService.getContentResolver();
             if (settings == null) {
-                return SCAN_MODE_LOW_POWER_WINDOW_MS;
+                return Settings.Global.getInt(
+                    resolver,
+                    Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS,
+                    SCAN_MODE_LOW_POWER_WINDOW_MS);
             }
+
             switch (settings.getScanMode()) {
                 case ScanSettings.SCAN_MODE_LOW_LATENCY:
-                    return SCAN_MODE_LOW_LATENCY_WINDOW_MS;
+                    return Settings.Global.getInt(
+                        resolver,
+                        Settings.Global.BLE_SCAN_LOW_LATENCY_WINDOW_MS,
+                        SCAN_MODE_LOW_LATENCY_WINDOW_MS);
                 case ScanSettings.SCAN_MODE_BALANCED:
-                    return SCAN_MODE_BALANCED_WINDOW_MS;
+                    return Settings.Global.getInt(
+                        resolver,
+                        Settings.Global.BLE_SCAN_BALANCED_WINDOW_MS,
+                        SCAN_MODE_BALANCED_WINDOW_MS);
                 case ScanSettings.SCAN_MODE_LOW_POWER:
-                    return SCAN_MODE_LOW_POWER_WINDOW_MS;
+                    return Settings.Global.getInt(
+                        resolver,
+                        Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS,
+                        SCAN_MODE_LOW_POWER_WINDOW_MS);
                 default:
-                    return SCAN_MODE_LOW_POWER_WINDOW_MS;
+                    return Settings.Global.getInt(
+                        resolver,
+                        Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS,
+                        SCAN_MODE_LOW_POWER_WINDOW_MS);
             }
         }
 
         private int getScanIntervalMillis(ScanSettings settings) {
-            if (settings == null)
-                return SCAN_MODE_LOW_POWER_INTERVAL_MS;
+            ContentResolver resolver = mService.getContentResolver();
+            if (settings == null) {
+                return Settings.Global.getInt(
+                    resolver,
+                    Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS,
+                    SCAN_MODE_LOW_POWER_INTERVAL_MS);
+            }
             switch (settings.getScanMode()) {
                 case ScanSettings.SCAN_MODE_LOW_LATENCY:
-                    return SCAN_MODE_LOW_LATENCY_INTERVAL_MS;
+                    return Settings.Global.getInt(
+                        resolver,
+                        Settings.Global.BLE_SCAN_LOW_LATENCY_INTERVAL_MS,
+                        SCAN_MODE_LOW_LATENCY_INTERVAL_MS);
                 case ScanSettings.SCAN_MODE_BALANCED:
-                    return SCAN_MODE_BALANCED_INTERVAL_MS;
+                    return Settings.Global.getInt(
+                        resolver,
+                        Settings.Global.BLE_SCAN_BALANCED_INTERVAL_MS,
+                        SCAN_MODE_BALANCED_INTERVAL_MS);
                 case ScanSettings.SCAN_MODE_LOW_POWER:
-                    return SCAN_MODE_LOW_POWER_INTERVAL_MS;
+                    return Settings.Global.getInt(
+                        resolver,
+                        Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS,
+                        SCAN_MODE_LOW_POWER_INTERVAL_MS);
                 default:
-                    return SCAN_MODE_LOW_POWER_INTERVAL_MS;
+                    return Settings.Global.getInt(
+                        resolver,
+                        Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS,
+                        SCAN_MODE_LOW_POWER_INTERVAL_MS);
             }
         }
 
@@ -1105,13 +1185,16 @@
             } else {
                 factor = MATCH_MODE_STICKY_TIMEOUT_FACTOR;
             }
-            if (!onFound) factor = factor * ONLOST_FACTOR;
-            return (timeout*factor);
+            if (!onFound) {
+                factor = factor * ONLOST_FACTOR;
+            }
+            return (timeout * factor);
         }
 
         private int getOnFoundOnLostSightings(ScanSettings settings) {
-            if (settings == null)
+            if (settings == null) {
                 return ONFOUND_SIGHTINGS_AGGRESSIVE;
+            }
             if (settings.getMatchMode() == ScanSettings.MATCH_MODE_AGGRESSIVE) {
                 return ONFOUND_SIGHTINGS_AGGRESSIVE;
             } else {
@@ -1120,9 +1203,10 @@
         }
 
         private int getNumOfTrackingAdvertisements(ScanSettings settings) {
-            if (settings == null)
+            if (settings == null) {
                 return 0;
-            int val=0;
+            }
+            int val = 0;
             int maxTotalTrackableAdvertisements =
                     AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements();
             // controller based onfound onlost resources are scarce commodity; the
@@ -1137,38 +1221,38 @@
                     val = 2;
                     break;
                 case ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT:
-                    val = maxTotalTrackableAdvertisements/2;
+                    val = maxTotalTrackableAdvertisements / 2;
                     break;
                 default:
                     val = 1;
                     if (DBG) {
                         Log.d(TAG, "Invalid setting for getNumOfMatches() "
-                                        + settings.getNumOfMatches());
+                                + settings.getNumOfMatches());
                     }
             }
             return val;
         }
 
         private boolean manageAllocationOfTrackingAdvertisement(int numOfTrackableAdvertisement,
-                            boolean allocate) {
+                boolean allocate) {
             int maxTotalTrackableAdvertisements =
                     AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements();
-            synchronized(curUsedTrackableAdvertisements) {
-                int availableEntries = maxTotalTrackableAdvertisements
-                                            - curUsedTrackableAdvertisements;
+            synchronized (mCurUsedTrackableAdvertisements) {
+                int availableEntries =
+                        maxTotalTrackableAdvertisements - mCurUsedTrackableAdvertisements;
                 if (allocate) {
                     if (availableEntries >= numOfTrackableAdvertisement) {
-                        curUsedTrackableAdvertisements += numOfTrackableAdvertisement;
+                        mCurUsedTrackableAdvertisements += numOfTrackableAdvertisement;
                         return true;
                     } else {
                         return false;
                     }
                 } else {
-                    if (numOfTrackableAdvertisement > curUsedTrackableAdvertisements) {
+                    if (numOfTrackableAdvertisement > mCurUsedTrackableAdvertisements) {
                         return false;
                     } else {
-                         curUsedTrackableAdvertisements -= numOfTrackableAdvertisement;
-                         return true;
+                        mCurUsedTrackableAdvertisements -= numOfTrackableAdvertisement;
+                        return true;
                     }
                 }
             }
@@ -1176,53 +1260,41 @@
 
 
         /************************** Regular scan related native methods **************************/
-        private native void registerScannerNative(long app_uuid_lsb, long app_uuid_msb);
+        private native void registerScannerNative(long appUuidLsb, long appUuidMsb);
+
         private native void unregisterScannerNative(int scannerId);
 
         private native void gattClientScanNative(boolean start);
 
-        private native void gattSetScanParametersNative(int client_if, int scan_interval,
-                int scan_window);
+        private native void gattSetScanParametersNative(int clientIf, int scanInterval,
+                int scanWindow);
 
         /************************** Filter related native methods ********************************/
-        private native void gattClientScanFilterAddNative(int client_if,
-                int filter_type, int filter_index, int company_id,
-                int company_id_mask, long uuid_lsb, long uuid_msb,
-                long uuid_mask_lsb, long uuid_mask_msb, String name,
-                String address, byte addr_type, byte[] data, byte[] mask);
+        private native void gattClientScanFilterAddNative(int clientId,
+                ScanFilterQueue.Entry[] entries, int filterIndex);
 
-        private native void gattClientScanFilterDeleteNative(int client_if,
-                int filter_type, int filter_index, int company_id,
-                int company_id_mask, long uuid_lsb, long uuid_msb,
-                long uuid_mask_lsb, long uuid_mask_msb, String name,
-                String address, byte addr_type, byte[] data, byte[] mask);
-
-        private native void gattClientScanFilterParamAddNative(FilterParams FiltValue);
+        private native void gattClientScanFilterParamAddNative(FilterParams filtValue);
 
         // Note this effectively remove scan filters for ALL clients.
-        private native void gattClientScanFilterParamClearAllNative(
-                int client_if);
+        private native void gattClientScanFilterParamClearAllNative(int clientIf);
 
-        private native void gattClientScanFilterParamDeleteNative(
-                int client_if, int filt_index);
+        private native void gattClientScanFilterParamDeleteNative(int clientIf, int filtIndex);
 
-        private native void gattClientScanFilterClearNative(int client_if,
-                int filter_index);
+        private native void gattClientScanFilterClearNative(int clientIf, int filterIndex);
 
-        private native void gattClientScanFilterEnableNative(int client_if,
-                boolean enable);
+        private native void gattClientScanFilterEnableNative(int clientIf, boolean enable);
 
         /************************** Batch related native methods *********************************/
-        private native void gattClientConfigBatchScanStorageNative(int client_if,
-                int max_full_reports_percent, int max_truncated_reports_percent,
-                int notify_threshold_percent);
+        private native void gattClientConfigBatchScanStorageNative(int clientIf,
+                int maxFullReportsPercent, int maxTruncatedReportsPercent,
+                int notifyThresholdPercent);
 
-        private native void gattClientStartBatchScanNative(int client_if, int scan_mode,
-                int scan_interval_unit, int scan_window_unit, int address_type, int discard_rule);
+        private native void gattClientStartBatchScanNative(int clientIf, int scanMode,
+                int scanIntervalUnit, int scanWindowUnit, int addressType, int discardRule);
 
-        private native void gattClientStopBatchScanNative(int client_if);
+        private native void gattClientStopBatchScanNative(int clientIf);
 
-        private native void gattClientReadScanReportsNative(int client_if, int scan_type);
+        private native void gattClientReadScanReportsNative(int clientIf, int scanType);
     }
 
     private boolean isScreenOn() {
@@ -1251,11 +1323,81 @@
 
                 @Override
                 public void onDisplayChanged(int displayId) {
-                    if (isScreenOn()) {
+                    if (isScreenOn() && mLocationManager.isLocationEnabled()) {
                         sendMessage(MSG_RESUME_SCANS, null);
                     } else {
                         sendMessage(MSG_SUSPEND_SCANS, null);
                     }
                 }
             };
+
+    private ActivityManager.OnUidImportanceListener mUidImportanceListener =
+            new ActivityManager.OnUidImportanceListener() {
+                @Override
+                public void onUidImportance(final int uid, final int importance) {
+                    if (mService.mScannerMap.getAppScanStatsByUid(uid) != null) {
+                        Message message = new Message();
+                        message.what = MSG_IMPORTANCE_CHANGE;
+                        message.obj = new UidImportance(uid, importance);
+                        mHandler.sendMessage(message);
+                    }
+                }
+            };
+
+    private BroadcastReceiver mLocationReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    String action = intent.getAction();
+                    if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
+                        final boolean locationEnabled = mLocationManager.isLocationEnabled();
+                        if (locationEnabled && isScreenOn()) {
+                            sendMessage(MSG_RESUME_SCANS, null);
+                        } else {
+                            sendMessage(MSG_SUSPEND_SCANS, null);
+                        }
+                    }
+                }
+            };
+
+    private void handleImportanceChange(UidImportance imp) {
+        if (imp == null) {
+            return;
+        }
+        int uid = imp.uid;
+        int importance = imp.importance;
+        boolean updatedScanParams = false;
+        if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+            for (ScanClient client : mRegularScanClients) {
+                if (client.appUid == uid && client.passiveSettings != null) {
+                    client.settings = client.passiveSettings;
+                    client.passiveSettings = null;
+                    updatedScanParams = true;
+                }
+            }
+        } else {
+            ContentResolver resolver = mService.getContentResolver();
+            int backgroundScanMode = Settings.Global.getInt(
+                    resolver,
+                    Settings.Global.BLE_SCAN_BACKGROUND_MODE,
+                    ScanSettings.SCAN_MODE_LOW_POWER);
+            for (ScanClient client : mRegularScanClients) {
+                if (client.appUid == uid && !mScanNative.isOpportunisticScanClient(client)) {
+                    client.passiveSettings = client.settings;
+                    ScanSettings.Builder builder = new ScanSettings.Builder();
+                    ScanSettings settings = client.settings;
+                    builder.setScanMode(backgroundScanMode);
+                    builder.setCallbackType(settings.getCallbackType());
+                    builder.setScanResultType(settings.getScanResultType());
+                    builder.setReportDelay(settings.getReportDelayMillis());
+                    builder.setNumOfMatches(settings.getNumOfMatches());
+                    client.settings = builder.build();
+                    updatedScanParams = true;
+                }
+            }
+        }
+        if (updatedScanParams) {
+            mScanNative.configureRegularScanParams();
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/hdp/HealthService.java b/src/com/android/bluetooth/hdp/HealthService.java
index ccae302..6a4af04 100644
--- a/src/com/android/bluetooth/hdp/HealthService.java
+++ b/src/com/android/bluetooth/hdp/HealthService.java
@@ -19,22 +19,23 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHealth;
 import android.bluetooth.BluetoothHealthAppConfiguration;
-import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothHealth;
 import android.bluetooth.IBluetoothHealthCallback;
-import android.content.Intent;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
 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.btservice.ProfileService;
-import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
+
+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;
@@ -55,11 +56,11 @@
 public class HealthService extends ProfileService {
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
-    private static final String TAG="HealthService";
+    private static final String TAG = "HealthService";
 
     private List<HealthChannel> mHealthChannels;
-    private Map <BluetoothHealthAppConfiguration, AppInfo> mApps;
-    private Map <BluetoothDevice, Integer> mHealthDevices;
+    private Map<BluetoothHealthAppConfiguration, AppInfo> mApps;
+    private Map<BluetoothDevice, Integer> mHealthDevices;
     private boolean mNativeAvailable;
     private HealthServiceMessageHandler mHandler;
     private static final int MESSAGE_REGISTER_APPLICATION = 1;
@@ -69,22 +70,22 @@
     private static final int MESSAGE_APP_REGISTRATION_CALLBACK = 11;
     private static final int MESSAGE_CHANNEL_STATE_CALLBACK = 12;
 
+    private static HealthService sHealthService;
+
     static {
         classInitNative();
     }
 
-    protected String getName() {
-        return TAG;
-    }
-
+    @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>());
+        mApps = Collections.synchronizedMap(
+                new HashMap<BluetoothHealthAppConfiguration, AppInfo>());
         mHealthDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>());
 
         HandlerThread thread = new HandlerThread("BluetoothHdpHandler");
@@ -92,11 +93,14 @@
         Looper looper = thread.getLooper();
         mHandler = new HealthServiceMessageHandler(looper);
         initializeNative();
-        mNativeAvailable=true;
+        mNativeAvailable = true;
+        setHealthService(this);
         return true;
     }
 
+    @Override
     protected boolean stop() {
+        setHealthService(null);
         if (mHandler != null) {
             mHandler.removeCallbacksAndMessages(null);
             Looper looper = mHandler.getLooper();
@@ -108,36 +112,63 @@
         return true;
     }
 
-    private void cleanupApps(){
+    private void cleanupApps() {
         if (mApps != null) {
-            Iterator<Map.Entry<BluetoothHealthAppConfiguration,AppInfo>> it
-                        = mApps.entrySet().iterator();
+            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();
+                Map.Entry<BluetoothHealthAppConfiguration, AppInfo> entry = it.next();
+                AppInfo appInfo = entry.getValue();
+                if (appInfo != null) {
+                    appInfo.cleanup();
+                }
+                it.remove();
             }
         }
     }
-    protected boolean cleanup() {
+
+    @Override
+    protected void cleanup() {
         mHandler = null;
         //Cleanup native
         if (mNativeAvailable) {
             cleanupNative();
-            mNativeAvailable=false;
+            mNativeAvailable = false;
         }
-        if(mHealthChannels != null) {
+        if (mHealthChannels != null) {
             mHealthChannels.clear();
         }
-        if(mHealthDevices != null) {
+        if (mHealthDevices != null) {
             mHealthDevices.clear();
         }
-        if(mApps != null) {
+        if (mApps != null) {
             mApps.clear();
         }
-        return true;
+    }
+
+    /**
+     * 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 {
@@ -147,113 +178,116 @@
 
         @Override
         public void handleMessage(Message msg) {
-            if (DBG) log("HealthService Handler msg: " + msg.what);
+            if (DBG) {
+                Log.d(TAG, "HealthService Handler msg: " + msg.what);
+            }
             switch (msg.what) {
-                case MESSAGE_REGISTER_APPLICATION:
-                {
+                case MESSAGE_REGISTER_APPLICATION: {
                     BluetoothHealthAppConfiguration appConfig =
-                        (BluetoothHealthAppConfiguration) msg.obj;
+                            (BluetoothHealthAppConfiguration) msg.obj;
                     AppInfo appInfo = mApps.get(appConfig);
-                    if (appInfo == null) break;
+                    if (appInfo == null) {
+                        break;
+                    }
                     int halRole = convertRoleToHal(appConfig.getRole());
                     int halChannelType = convertChannelTypeToHal(appConfig.getChannelType());
-                    if (VDBG) log("register datatype: " + appConfig.getDataType() + " role: " +
-                                 halRole + " name: " + appConfig.getName() + " channeltype: " +
-                                 halChannelType);
+                    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);
+                            appConfig.getName(), halChannelType);
                     if (appId == -1) {
                         callStatusCallback(appConfig,
-                                           BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE);
+                                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);
+                        appInfo.mRcpObj =
+                                new BluetoothHealthDeathRecipient(HealthService.this, appConfig);
                         IBinder binder = appInfo.mCallback.asBinder();
                         try {
-                            binder.linkToDeath(appInfo.mRcpObj,0);
+                            binder.linkToDeath(appInfo.mRcpObj, 0);
                         } catch (RemoteException e) {
-                            Log.e(TAG,"LinktoDeath Exception:"+e);
+                            Log.e(TAG, "LinktoDeath Exception:" + e);
                         }
                         appInfo.mAppId = appId;
                         callStatusCallback(appConfig,
-                                           BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS);
+                                BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS);
                     }
                 }
-                    break;
-                case MESSAGE_UNREGISTER_APPLICATION:
-                {
+                break;
+                case MESSAGE_UNREGISTER_APPLICATION: {
                     BluetoothHealthAppConfiguration appConfig =
-                        (BluetoothHealthAppConfiguration) msg.obj;
+                            (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);
+                                BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE);
                     }
                 }
-                    break;
-                case MESSAGE_CONNECT_CHANNEL:
-                {
+                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);
+                                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);
+                                BluetoothHealth.STATE_CHANNEL_DISCONNECTED,
+                                BluetoothHealth.STATE_CHANNEL_DISCONNECTING, chan.mChannelFd,
+                                chan.mChannelId);
                     }
                 }
-                    break;
-                case MESSAGE_DISCONNECT_CHANNEL:
-                {
+                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);
+                                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);
+                                BluetoothHealth.STATE_CHANNEL_CONNECTED,
+                                BluetoothHealth.STATE_CHANNEL_DISCONNECTING, chan.mChannelFd,
+                                chan.mChannelId);
                     }
                 }
-                    break;
-                case MESSAGE_APP_REGISTRATION_CALLBACK:
-                {
+                break;
+                case MESSAGE_APP_REGISTRATION_CALLBACK: {
                     BluetoothHealthAppConfiguration appConfig = findAppConfigByAppId(msg.arg1);
-                    if (appConfig == null) break;
+                    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) {
+                    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:
-                {
+                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");
+                    if (newState == BluetoothHealth.STATE_CHANNEL_DISCONNECTED
+                            && appConfig == null) {
+                        Log.e(TAG, "Disconnected for non existing app");
                         break;
                     }
                     if (chan == null) {
@@ -272,39 +306,42 @@
                             Log.e(TAG, "failed to dup ParcelFileDescriptor");
                             break;
                         }
-                    }
-                    /*set the channel fd to null if channel state isnot equal to connected*/
-                    else{
+                    } 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);
+                    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;
+                break;
             }
         }
     }
 
-//Handler for DeathReceipient
-    private static class BluetoothHealthDeathRecipient implements IBinder.DeathRecipient{
+    //Handler for DeathReceipient
+    private static class BluetoothHealthDeathRecipient implements IBinder.DeathRecipient {
         private BluetoothHealthAppConfiguration mConfig;
         private HealthService mService;
 
-        public BluetoothHealthDeathRecipient(HealthService service, BluetoothHealthAppConfiguration config) {
+        BluetoothHealthDeathRecipient(HealthService service,
+                BluetoothHealthAppConfiguration config) {
             mService = service;
             mConfig = config;
         }
 
+        @Override
         public void binderDied() {
-            if (DBG) Log.d(TAG,"Binder is dead.");
+            if (DBG) {
+                Log.d(TAG, "Binder is dead.");
+            }
             mService.unregisterAppConfiguration(mConfig);
         }
 
-        public void cleanup(){
+        public void cleanup() {
             mService = null;
             mConfig = null;
         }
@@ -313,94 +350,123 @@
     /**
      * Handlers for incoming service calls
      */
-    private static class BluetoothHealthBinder extends IBluetoothHealth.Stub implements IProfileServiceBinder {
+    private static class BluetoothHealthBinder extends IBluetoothHealth.Stub
+            implements IProfileServiceBinder {
         private HealthService mService;
 
-        public BluetoothHealthBinder(HealthService svc) {
+        BluetoothHealthBinder(HealthService svc) {
             mService = svc;
         }
 
-        public boolean cleanup()  {
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
         private HealthService getService() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"Health call not allowed for non-active user");
+                Log.w(TAG, "Health call not allowed for non-active user");
                 return null;
             }
 
-            if (mService  != null && mService.isAvailable()) {
+            if (mService != null && mService.isAvailable()) {
                 return mService;
             }
             return null;
         }
 
+        @Override
         public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
-                                                IBluetoothHealthCallback callback) {
+                IBluetoothHealthCallback callback) {
             HealthService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.registerAppConfiguration(config, callback);
         }
 
+        @Override
         public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
             HealthService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.unregisterAppConfiguration(config);
         }
 
+        @Override
         public boolean connectChannelToSource(BluetoothDevice device,
-                                              BluetoothHealthAppConfiguration config) {
+                BluetoothHealthAppConfiguration config) {
             HealthService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.connectChannelToSource(device, config);
         }
 
+        @Override
         public boolean connectChannelToSink(BluetoothDevice device,
-                           BluetoothHealthAppConfiguration config, int channelType) {
+                BluetoothHealthAppConfiguration config, int channelType) {
             HealthService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.connectChannelToSink(device, config, channelType);
         }
 
+        @Override
         public boolean disconnectChannel(BluetoothDevice device,
-                                         BluetoothHealthAppConfiguration config, int channelId) {
+                BluetoothHealthAppConfiguration config, int channelId) {
             HealthService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.disconnectChannel(device, config, channelId);
         }
 
+        @Override
         public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
-                                                     BluetoothHealthAppConfiguration config) {
+                BluetoothHealthAppConfiguration config) {
             HealthService service = getService();
-            if (service == null) return null;
+            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;
+            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);
+            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);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getHealthDevicesMatchingConnectionStates(states);
         }
-    };
+    }
+
+    ;
 
     boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
             IBluetoothHealthCallback callback) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM,
-                "Need BLUETOOTH permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
 
         if (config == null) {
             Log.e(TAG, "Trying to use a null config for registration");
@@ -408,11 +474,13 @@
         }
 
         if (mApps.get(config) != null) {
-            if (DBG) Log.d(TAG, "Config has already been registered");
+            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);
+        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION, config);
         mHandler.sendMessage(msg);
         return true;
     }
@@ -420,44 +488,47 @@
     boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (mApps.get(config) == null) {
-            if (DBG) Log.d(TAG,"unregisterAppConfiguration: no app found");
+            if (DBG) {
+                Log.d(TAG, "unregisterAppConfiguration: no app found");
+            }
             return false;
         }
-        Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION,config);
+        Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION, config);
         mHandler.sendMessage(msg);
         return true;
     }
 
-    boolean connectChannelToSource(BluetoothDevice device,
-                                          BluetoothHealthAppConfiguration config) {
+    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) {
+    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) {
+    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");
+            if (DBG) {
+                Log.d(TAG, "disconnectChannel: no channel found");
+            }
             return false;
         }
-        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT_CHANNEL,chan);
+        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT_CHANNEL, chan);
         mHandler.sendMessage(msg);
         return true;
     }
 
     ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
-                                                 BluetoothHealthAppConfiguration config) {
+            BluetoothHealthAppConfiguration config) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HealthChannel healthChan = null;
-        for (HealthChannel chan: mHealthChannels) {
+        for (HealthChannel chan : mHealthChannels) {
             if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) {
                 healthChan = chan;
             }
@@ -476,8 +547,8 @@
 
     List<BluetoothDevice> getConnectedHealthDevices() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(
-                new int[] {BluetoothHealth.STATE_CONNECTED});
+        List<BluetoothDevice> devices =
+                lookupHealthDevicesMatchingStates(new int[]{BluetoothHealth.STATE_CONNECTED});
         return devices;
     }
 
@@ -494,11 +565,11 @@
         mHandler.sendMessage(msg);
     }
 
-    private void onChannelStateChanged(int appId, byte[] addr, int cfgIndex,
-                                       int channelId, int state, FileDescriptor pfd) {
+    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);
+        ChannelStateEvent channelStateEvent =
+                new ChannelStateEvent(appId, addr, cfgIndex, channelId, state, pfd);
         msg.obj = channelStateEvent;
         mHandler.sendMessage(msg);
     }
@@ -514,7 +585,9 @@
     }
 
     private void callStatusCallback(BluetoothHealthAppConfiguration config, int status) {
-        if (VDBG) log ("Health Device Application: " + config + " State Change: status:" + 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");
@@ -575,8 +648,8 @@
         }
     }
 
-    private boolean connectChannel(BluetoothDevice device,
-                                   BluetoothHealthAppConfiguration config, int channelType) {
+    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;
@@ -595,8 +668,8 @@
             BluetoothDevice device, int state, int prevState, ParcelFileDescriptor fd, int id) {
         broadcastHealthDeviceStateChange(device, state);
 
-        log("Health Device Callback: " + device + " State Change: " + prevState + "->" +
-                     state);
+        Log.d(TAG,
+                "Health Device Callback: " + device + " State Change: " + prevState + "->" + state);
 
         ParcelFileDescriptor dupedFd = null;
         if (fd != null) {
@@ -648,7 +721,9 @@
         int currDeviceState = mHealthDevices.get(device);
         int newDeviceState = convertState(newChannelState);
 
-        if (currDeviceState == newDeviceState) return;
+        if (currDeviceState == newDeviceState) {
+            return;
+        }
 
         boolean sendIntent = false;
         List<HealthChannel> chan;
@@ -665,9 +740,10 @@
                     sendIntent = true;
                 } else {
                     // Channel got disconnected
-                    chan = findChannelByStates(device, new int [] {
+                    chan = findChannelByStates(device, new int[]{
                             BluetoothHealth.STATE_CHANNEL_CONNECTING,
-                            BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
+                            BluetoothHealth.STATE_CHANNEL_DISCONNECTING
+                    });
                     if (chan.isEmpty()) {
                         sendIntent = true;
                     }
@@ -677,9 +753,10 @@
                 // there was at least one connection
 
                 // Channel got disconnected or is in disconnecting state.
-                chan = findChannelByStates(device, new int [] {
+                chan = findChannelByStates(device, new int[]{
                         BluetoothHealth.STATE_CHANNEL_CONNECTING,
-                        BluetoothHealth.STATE_CHANNEL_CONNECTED});
+                        BluetoothHealth.STATE_CHANNEL_CONNECTED
+                });
                 if (chan.isEmpty()) {
                     sendIntent = true;
                 }
@@ -689,16 +766,18 @@
                 // We were disconnecting all the channels with the remote device
 
                 // Channel got disconnected.
-                chan = findChannelByStates(device, new int [] {
+                chan = findChannelByStates(device, new int[]{
                         BluetoothHealth.STATE_CHANNEL_CONNECTING,
-                        BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
+                        BluetoothHealth.STATE_CHANNEL_DISCONNECTING
+                });
                 if (chan.isEmpty()) {
                     updateAndSendIntent(device, newDeviceState, currDeviceState);
                 }
                 break;
         }
-        if (sendIntent)
+        if (sendIntent) {
             updateAndSendIntent(device, newDeviceState, currDeviceState);
+        }
     }
 
     private void updateAndSendIntent(BluetoothDevice device, int newDeviceState,
@@ -708,6 +787,10 @@
         } else {
             mHealthDevices.put(device, newDeviceState);
         }
+        if (newDeviceState != prevDeviceState
+                && newDeviceState == BluetoothHealth.STATE_CONNECTED) {
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEALTH);
+        }
     }
 
     /**
@@ -732,23 +815,35 @@
     }
 
     private int convertRoleToHal(int role) {
-        if (role == BluetoothHealth.SOURCE_ROLE) return MDEP_ROLE_SOURCE;
-        if (role == BluetoothHealth.SINK_ROLE) return MDEP_ROLE_SINK;
+        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;
+        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;
+            if (chan.mChannelId == id) {
+                return chan;
+            }
         }
         Log.e(TAG, "No channel found by id: " + id);
         return null;
@@ -756,7 +851,7 @@
 
     private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) {
         List<HealthChannel> channels = new ArrayList<HealthChannel>();
-        for (HealthChannel chan: mHealthChannels) {
+        for (HealthChannel chan : mHealthChannels) {
             if (chan.mDevice.equals(device)) {
                 for (int state : states) {
                     if (chan.mState == state) {
@@ -778,7 +873,7 @@
     List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) {
         List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>();
 
-        for (BluetoothDevice device: mHealthDevices.keySet()) {
+        for (BluetoothDevice device : mHealthDevices.keySet()) {
             int healthDeviceState = getConnectionState(device);
             for (int state : states) {
                 if (state == healthDeviceState) {
@@ -818,25 +913,24 @@
             mAppId = -1;
         }
 
-        private void cleanup(){
-            if(mCallback != null){
-                if(mRcpObj != null){
+        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);
+                    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;
             }
-            else if(mRcpObj != null){
-                    mRcpObj.cleanup();
-                    mRcpObj = null;
-            }
-       }
+        }
     }
 
     private class HealthChannel {
@@ -849,13 +943,13 @@
         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;
+                int channelType) {
+            mChannelFd = null;
+            mDevice = device;
+            mConfig = config;
+            mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+            mChannelType = channelType;
+            mChannelId = -1;
         }
     }
 
@@ -868,8 +962,8 @@
         int mState;
         FileDescriptor mFd;
 
-        private ChannelStateEvent(int appId, byte[] addr, int cfgIndex,
-                                  int channelId, int state, FileDescriptor fileDescriptor) {
+        private ChannelStateEvent(int appId, byte[] addr, int cfgIndex, int channelId, int state,
+                FileDescriptor fileDescriptor) {
             mAppId = appId;
             mAddr = addr;
             mCfgIndex = cfgIndex;
@@ -900,14 +994,21 @@
     // 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 final int CHANNEL_TYPE_ANY = 2;
 
-    private native static void classInitNative();
+    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 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
new file mode 100644
index 0000000..e735a88
--- /dev/null
+++ b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
@@ -0,0 +1,196 @@
+/*
+ * 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.
+ */
+
+/*
+ * Defines the native interface that is used by state machine/service to
+ * send or receive messages from the native stack. This file is registered
+ * for the native methods in the corresponding JNI C++ file.
+ */
+package com.android.bluetooth.hearingaid;
+
+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;
+
+/**
+ * HearingAid Native Interface to/from JNI.
+ */
+public class HearingAidNativeInterface {
+    private static final String TAG = "HearingAidNativeInterface";
+    private static final boolean DBG = true;
+    private BluetoothAdapter mAdapter;
+
+    @GuardedBy("INSTANCE_LOCK")
+    private static HearingAidNativeInterface sInstance;
+    private static final Object INSTANCE_LOCK = new Object();
+
+    static {
+        classInitNative();
+    }
+
+    private HearingAidNativeInterface() {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mAdapter == null) {
+            Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+        }
+    }
+
+    /**
+     * Get singleton instance.
+     */
+    public static HearingAidNativeInterface getInstance() {
+        synchronized (INSTANCE_LOCK) {
+            if (sInstance == null) {
+                sInstance = new HearingAidNativeInterface();
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Initializes the native interface.
+     *
+     * priorities to configure.
+     */
+    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public void init() {
+        initNative();
+    }
+
+    /**
+     * Cleanup the native interface.
+     */
+    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public void cleanup() {
+        cleanupNative();
+    }
+
+    /**
+     * Initiates HearingAid connection to a remote device.
+     *
+     * @param device the remote device
+     * @return true on success, otherwise false.
+     */
+    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public boolean connectHearingAid(BluetoothDevice device) {
+        return connectHearingAidNative(getByteAddress(device));
+    }
+
+    /**
+     * Disconnects HearingAid from a remote device.
+     *
+     * @param device the remote device
+     * @return true on success, otherwise false.
+     */
+    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public boolean disconnectHearingAid(BluetoothDevice device) {
+        return disconnectHearingAidNative(getByteAddress(device));
+    }
+
+    /**
+     * Add a hearing aid device to white list.
+     *
+     * @param device the remote device
+     * @return true on success, otherwise false.
+     */
+    @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    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)
+    public void setVolume(int volume) {
+        setVolumeNative(volume);
+    }
+
+    private BluetoothDevice getDevice(byte[] address) {
+        return mAdapter.getRemoteDevice(address);
+    }
+
+    private byte[] getByteAddress(BluetoothDevice device) {
+        if (device == null) {
+            return Utils.getBytesFromAddress("00:00:00:00:00:00");
+        }
+        return Utils.getBytesFromAddress(device.getAddress());
+    }
+
+    private void sendMessageToService(HearingAidStackEvent event) {
+        HearingAidService service = HearingAidService.getHearingAidService();
+        if (service != null) {
+            service.messageFromNative(event);
+        } else {
+            Log.e(TAG, "Event ignored, service not available: " + event);
+        }
+    }
+
+    // Callbacks from the native stack back into the Java framework.
+    // All callbacks are routed via the Service which will disambiguate which
+    // state machine the message should be routed to.
+
+    private void onConnectionStateChanged(int state, byte[] address) {
+        HearingAidStackEvent event =
+                new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.device = getDevice(address);
+        event.valueInt1 = state;
+
+        if (DBG) {
+            Log.d(TAG, "onConnectionStateChanged: " + event);
+        }
+        sendMessageToService(event);
+    }
+
+    private void onDeviceAvailable(byte capabilities, long hiSyncId, byte[] address) {
+        HearingAidStackEvent event = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
+        event.device = getDevice(address);
+        event.valueInt1 = capabilities;
+        event.valueLong2 = hiSyncId;
+
+        if (DBG) {
+            Log.d(TAG, "onDeviceAvailable: " + event);
+        }
+        sendMessageToService(event);
+    }
+
+    // Native methods that call into the JNI interface
+    private static native void classInitNative();
+    private native void initNative();
+    private native void cleanupNative();
+    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
new file mode 100644
index 0000000..704a3d5
--- /dev/null
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -0,0 +1,929 @@
+/*
+ * 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.hearingaid;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.IBluetoothHearingAid;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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 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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Provides Bluetooth HearingAid profile, as a service in the Bluetooth application.
+ * @hide
+ */
+public class HearingAidService extends ProfileService {
+    private static final boolean DBG = false;
+    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;
+    private BluetoothDevice mPreviousAudioDevice;
+
+    @VisibleForTesting
+    HearingAidNativeInterface mHearingAidNativeInterface;
+    @VisibleForTesting
+    AudioManager mAudioManager;
+
+    private final Map<BluetoothDevice, HearingAidStateMachine> mStateMachines =
+            new HashMap<>();
+    private final Map<BluetoothDevice, Long> mDeviceHiSyncIdMap = new ConcurrentHashMap<>();
+    private final Map<BluetoothDevice, Integer> mDeviceCapabilitiesMap = new HashMap<>();
+    private long mActiveDeviceHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
+
+    private BroadcastReceiver mBondStateChangedReceiver;
+    private BroadcastReceiver mConnectionStateChangedReceiver;
+
+    @Override
+    protected IProfileServiceBinder initBinder() {
+        return new BluetoothHearingAidBinder(this);
+    }
+
+    @Override
+    protected void create() {
+        if (DBG) {
+            Log.d(TAG, "create()");
+        }
+    }
+
+    @Override
+    protected boolean start() {
+        if (DBG) {
+            Log.d(TAG, "start()");
+        }
+        if (sHearingAidService != null) {
+            throw new IllegalStateException("start() called twice");
+        }
+
+        // Get AdapterService, HearingAidNativeInterface, AudioManager.
+        // None of them can be null.
+        mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+                "AdapterService cannot be null when HearingAidService starts");
+        mHearingAidNativeInterface = Objects.requireNonNull(HearingAidNativeInterface.getInstance(),
+                "HearingAidNativeInterface cannot be null when HearingAidService starts");
+        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        Objects.requireNonNull(mAudioManager,
+                "AudioManager cannot be null when HearingAidService starts");
+
+        // Start handler thread for state machines
+        mStateMachines.clear();
+        mStateMachinesThread = new HandlerThread("HearingAidService.StateMachines");
+        mStateMachinesThread.start();
+
+        // Clear HiSyncId map and capabilities map
+        mDeviceHiSyncIdMap.clear();
+        mDeviceCapabilitiesMap.clear();
+
+        // Setup broadcast receivers
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        mBondStateChangedReceiver = new BondStateChangedReceiver();
+        registerReceiver(mBondStateChangedReceiver, filter);
+        filter = new IntentFilter();
+        filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+        mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
+        registerReceiver(mConnectionStateChangedReceiver, filter);
+
+        // Mark service as started
+        setHearingAidService(this);
+
+        // Initialize native interface
+        mHearingAidNativeInterface.init();
+
+        return true;
+    }
+
+    @Override
+    protected boolean stop() {
+        if (DBG) {
+            Log.d(TAG, "stop()");
+        }
+        if (sHearingAidService == null) {
+            Log.w(TAG, "stop() called before start()");
+            return true;
+        }
+
+        // Cleanup native interface
+        mHearingAidNativeInterface.cleanup();
+        mHearingAidNativeInterface = null;
+
+        // Mark service as stopped
+        setHearingAidService(null);
+
+        // Unregister broadcast receivers
+        unregisterReceiver(mBondStateChangedReceiver);
+        mBondStateChangedReceiver = null;
+        unregisterReceiver(mConnectionStateChangedReceiver);
+        mConnectionStateChangedReceiver = null;
+
+        // Destroy state machines and stop handler thread
+        synchronized (mStateMachines) {
+            for (HearingAidStateMachine sm : mStateMachines.values()) {
+                sm.doQuit();
+                sm.cleanup();
+            }
+            mStateMachines.clear();
+        }
+
+        // Clear HiSyncId map and capabilities map
+        mDeviceHiSyncIdMap.clear();
+        mDeviceCapabilitiesMap.clear();
+
+        if (mStateMachinesThread != null) {
+            mStateMachinesThread.quitSafely();
+            mStateMachinesThread = null;
+        }
+
+        // Clear AdapterService, HearingAidNativeInterface
+        mAudioManager = null;
+        mHearingAidNativeInterface = null;
+        mAdapterService = null;
+
+        return true;
+    }
+
+    @Override
+    protected void cleanup() {
+        if (DBG) {
+            Log.d(TAG, "cleanup()");
+        }
+    }
+
+    /**
+     * Get the HearingAidService instance
+     * @return HearingAidService instance
+     */
+    public static synchronized HearingAidService getHearingAidService() {
+        if (sHearingAidService == null) {
+            Log.w(TAG, "getHearingAidService(): service is NULL");
+            return null;
+        }
+
+        if (!sHearingAidService.isAvailable()) {
+            Log.w(TAG, "getHearingAidService(): service is not available");
+            return null;
+        }
+        return sHearingAidService;
+    }
+
+    private static synchronized void setHearingAidService(HearingAidService instance) {
+        if (DBG) {
+            Log.d(TAG, "setHearingAidService(): set to: " + instance);
+        }
+        sHearingAidService = instance;
+    }
+
+    boolean connect(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "connect(): " + device);
+        }
+        if (device == null) {
+            return false;
+        }
+
+        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
+            return false;
+        }
+        ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
+        if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.HearingAid)) {
+            Log.e(TAG, "Cannot connect to " + device + " : Remote does not have Hearing Aid UUID");
+            return false;
+        }
+
+        long hiSyncId = mDeviceHiSyncIdMap.getOrDefault(device,
+                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+
+        if (hiSyncId != mActiveDeviceHiSyncId
+                && hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID
+                && mActiveDeviceHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+            for (BluetoothDevice connectedDevice : getConnectedDevices()) {
+                disconnect(connectedDevice);
+            }
+        }
+
+        for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) {
+            if (device.equals(storedDevice)) {
+                continue;
+            }
+            if (mDeviceHiSyncIdMap.getOrDefault(storedDevice,
+                    BluetoothHearingAid.HI_SYNC_ID_INVALID) == hiSyncId) {
+                synchronized (mStateMachines) {
+                    HearingAidStateMachine sm = getOrCreateStateMachine(storedDevice);
+                    if (sm == null) {
+                        Log.e(TAG, "Ignored connect request for " + device + " : no state machine");
+                        continue;
+                    }
+                    sm.sendMessage(HearingAidStateMachine.CONNECT,
+                            sConnectTimeoutForEachSideMs);
+                    sm.sendMessageDelayed(HearingAidStateMachine.CHECK_WHITELIST_CONNECTION,
+                            sCheckWhitelistTimeoutMs);
+                }
+                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;
+    }
+
+    boolean disconnect(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "disconnect(): " + device);
+        }
+        if (device == null) {
+            return false;
+        }
+        long hiSyncId = mDeviceHiSyncIdMap.getOrDefault(device,
+                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+
+        for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) {
+            if (mDeviceHiSyncIdMap.getOrDefault(storedDevice,
+                    BluetoothHearingAid.HI_SYNC_ID_INVALID) == hiSyncId) {
+                synchronized (mStateMachines) {
+                    HearingAidStateMachine sm = mStateMachines.get(storedDevice);
+                    if (sm == null) {
+                        Log.e(TAG, "Ignored disconnect request for " + device
+                                + " : no state machine");
+                        continue;
+                    }
+                    sm.sendMessage(HearingAidStateMachine.DISCONNECT);
+                }
+                if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID
+                        && !device.equals(storedDevice)) {
+                    break;
+                }
+            }
+        }
+        return true;
+    }
+
+    List<BluetoothDevice> getConnectedDevices() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        synchronized (mStateMachines) {
+            List<BluetoothDevice> devices = new ArrayList<>();
+            for (HearingAidStateMachine sm : mStateMachines.values()) {
+                if (sm.isConnected()) {
+                    devices.add(sm.getDevice());
+                }
+            }
+            return devices;
+        }
+    }
+
+    /**
+     * 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)
+    public boolean okToConnect(BluetoothDevice device) {
+        // Check if this is an incoming connection in Quiet mode.
+        if (mAdapterService.isQuietModeEnabled()) {
+            Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
+            return false;
+        }
+        // Check priority and accept or reject the connection.
+        int priority = getPriority(device);
+        int bondState = mAdapterService.getBondState(device);
+        // Allow this connection only if the device is bonded. Any attempt to connect while
+        // bonding would potentially lead to an unauthorized connection.
+        if (bondState != BluetoothDevice.BOND_BONDED) {
+            Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
+            return false;
+        } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED
+                && priority != BluetoothProfile.PRIORITY_ON
+                && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+            // Otherwise, reject the connection if priority is not valid.
+            Log.w(TAG, "okToConnect: return false, priority=" + priority);
+            return false;
+        }
+        return true;
+    }
+
+    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) {
+            for (BluetoothDevice device : bondedDevices) {
+                final ParcelUuid[] featureUuids = device.getUuids();
+                if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.HearingAid)) {
+                    continue;
+                }
+                int connectionState = BluetoothProfile.STATE_DISCONNECTED;
+                HearingAidStateMachine sm = mStateMachines.get(device);
+                if (sm != null) {
+                    connectionState = sm.getConnectionState();
+                }
+                for (int state : states) {
+                    if (connectionState == state) {
+                        devices.add(device);
+                        break;
+                    }
+                }
+            }
+            return devices;
+        }
+    }
+
+    /**
+     * Get the list of devices that have state machines.
+     *
+     * @return the list of devices that have state machines
+     */
+    @VisibleForTesting
+    List<BluetoothDevice> getDevices() {
+        List<BluetoothDevice> devices = new ArrayList<>();
+        synchronized (mStateMachines) {
+            for (HearingAidStateMachine sm : mStateMachines.values()) {
+                devices.add(sm.getDevice());
+            }
+            return devices;
+        }
+    }
+
+    /**
+     * Get the HiSyncIdMap for testing
+     *
+     * @return mDeviceHiSyncIdMap
+     */
+    @VisibleForTesting
+    Map<BluetoothDevice, Long> getHiSyncIdMap() {
+        return mDeviceHiSyncIdMap;
+    }
+
+    int getConnectionState(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        synchronized (mStateMachines) {
+            HearingAidStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+            return sm.getConnectionState();
+        }
+    }
+
+    /**
+     * Set the priority of the Hearing Aid 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");
+        Settings.Global.putInt(getContentResolver(),
+                Settings.Global.getBluetoothHearingAidPriorityKey(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.getBluetoothHearingAidPriorityKey(device.getAddress()),
+                BluetoothProfile.PRIORITY_UNDEFINED);
+        return priority;
+    }
+
+    void setVolume(int volume) {
+        mHearingAidNativeInterface.setVolume(volume);
+    }
+
+    long getHiSyncId(BluetoothDevice device) {
+        if (device == null) {
+            return BluetoothHearingAid.HI_SYNC_ID_INVALID;
+        }
+        return mDeviceHiSyncIdMap.getOrDefault(device, BluetoothHearingAid.HI_SYNC_ID_INVALID);
+    }
+
+    int getCapabilities(BluetoothDevice device) {
+        return mDeviceCapabilitiesMap.getOrDefault(device, -1);
+    }
+
+    /**
+     * Set the active device.
+     * @param device the new active device
+     * @return true on success, otherwise false
+     */
+    public boolean setActiveDevice(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "setActiveDevice:" + device);
+        }
+        synchronized (mStateMachines) {
+            if (device == null) {
+                if (mActiveDeviceHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                    reportActiveDevice(null);
+                    mActiveDeviceHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
+                }
+                return true;
+            }
+            if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
+                Log.e(TAG, "setActiveDevice(" + device + "): failed because device not connected");
+                return false;
+            }
+            Long deviceHiSyncId = mDeviceHiSyncIdMap.getOrDefault(device,
+                    BluetoothHearingAid.HI_SYNC_ID_INVALID);
+            if (deviceHiSyncId != mActiveDeviceHiSyncId) {
+                mActiveDeviceHiSyncId = deviceHiSyncId;
+                reportActiveDevice(device);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Get the connected physical Hearing Aid devices that are active
+     *
+     * @return the list of active devices. The first element is the left active
+     * 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() {
+        if (DBG) {
+            Log.d(TAG, "getActiveDevices");
+        }
+        ArrayList<BluetoothDevice> activeDevices = new ArrayList<>();
+        activeDevices.add(null);
+        activeDevices.add(null);
+        synchronized (mStateMachines) {
+            if (mActiveDeviceHiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                return activeDevices;
+            }
+            for (BluetoothDevice device : mDeviceHiSyncIdMap.keySet()) {
+                if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
+                    continue;
+                }
+                if (mDeviceHiSyncIdMap.get(device) == mActiveDeviceHiSyncId) {
+                    int deviceSide = getCapabilities(device) & 1;
+                    if (deviceSide == BluetoothHearingAid.SIDE_RIGHT) {
+                        activeDevices.set(1, device);
+                    } else {
+                        activeDevices.set(0, device);
+                    }
+                }
+            }
+        }
+        return activeDevices;
+    }
+
+    void messageFromNative(HearingAidStackEvent stackEvent) {
+        Objects.requireNonNull(stackEvent.device,
+                "Device should never be null, event: " + stackEvent);
+
+        if (stackEvent.type == HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE) {
+            BluetoothDevice device = stackEvent.device;
+            int capabilities = stackEvent.valueInt1;
+            long hiSyncId = stackEvent.valueLong2;
+            if (DBG) {
+                Log.d(TAG, "Device available: device=" + device + " capabilities="
+                        + capabilities + " hiSyncId=" + hiSyncId);
+            }
+            mDeviceCapabilitiesMap.put(device, capabilities);
+            mDeviceHiSyncIdMap.put(device, hiSyncId);
+            return;
+        }
+
+        synchronized (mStateMachines) {
+            BluetoothDevice device = stackEvent.device;
+            HearingAidStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                if (stackEvent.type == HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
+                    switch (stackEvent.valueInt1) {
+                        case HearingAidStackEvent.CONNECTION_STATE_CONNECTED:
+                        case HearingAidStackEvent.CONNECTION_STATE_CONNECTING:
+                            sm = getOrCreateStateMachine(device);
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            }
+            if (sm == null) {
+                Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
+                return;
+            }
+            sm.sendMessage(HearingAidStateMachine.STACK_EVENT, stackEvent);
+        }
+    }
+
+    private HearingAidStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+        if (device == null) {
+            Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
+            return null;
+        }
+        synchronized (mStateMachines) {
+            HearingAidStateMachine sm = mStateMachines.get(device);
+            if (sm != null) {
+                return sm;
+            }
+            // Limit the maximum number of state machines to avoid DoS attack
+            if (mStateMachines.size() >= MAX_HEARING_AID_STATE_MACHINES) {
+                Log.e(TAG, "Maximum number of HearingAid state machines reached: "
+                        + MAX_HEARING_AID_STATE_MACHINES);
+                return null;
+            }
+            if (DBG) {
+                Log.d(TAG, "Creating a new state machine for " + device);
+            }
+            sm = HearingAidStateMachine.make(device, this,
+                    mHearingAidNativeInterface, mStateMachinesThread.getLooper());
+            mStateMachines.put(device, sm);
+            return sm;
+        }
+    }
+
+    /**
+     * Report the active device change to the active device manager and the media framework.
+     * @param device the new active device; or null if no active device
+     */
+    private void reportActiveDevice(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "reportActiveDevice(" + 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
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+
+        if (device == null) {
+            if (DBG) {
+                Log.d(TAG, "Set Hearing Aid audio to disconnected");
+            }
+            boolean suppressNoisyIntent =
+                    (getConnectionState(mPreviousAudioDevice) == BluetoothProfile.STATE_CONNECTED);
+            mAudioManager.setBluetoothHearingAidDeviceConnectionState(
+                    mPreviousAudioDevice, BluetoothProfile.STATE_DISCONNECTED,
+                    suppressNoisyIntent, 0);
+            mPreviousAudioDevice = null;
+        } else {
+            if (DBG) {
+                Log.d(TAG, "Set Hearing Aid audio to connected");
+            }
+            if (mPreviousAudioDevice != null) {
+                mAudioManager.setBluetoothHearingAidDeviceConnectionState(
+                        mPreviousAudioDevice, BluetoothProfile.STATE_DISCONNECTED,
+                        true, 0);
+            }
+            mAudioManager.setBluetoothHearingAidDeviceConnectionState(
+                    device, BluetoothProfile.STATE_CONNECTED,
+                    true, 0);
+            mPreviousAudioDevice = device;
+        }
+    }
+
+    // Remove state machine if the bonding for a device is removed
+    private class BondStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            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);
+        }
+    }
+
+    /**
+     * Process a change in the bonding state for a device.
+     *
+     * @param device the device whose bonding state has changed
+     * @param bondState the new bond state for the device. Possible values are:
+     * {@link BluetoothDevice#BOND_NONE},
+     * {@link BluetoothDevice#BOND_BONDING},
+     * {@link BluetoothDevice#BOND_BONDED}.
+     */
+    @VisibleForTesting
+    void bondStateChanged(BluetoothDevice device, int bondState) {
+        if (DBG) {
+            Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState);
+        }
+        // Remove state machine if the bonding for a device is removed
+        if (bondState != BluetoothDevice.BOND_NONE) {
+            return;
+        }
+        mDeviceHiSyncIdMap.remove(device);
+        synchronized (mStateMachines) {
+            HearingAidStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                return;
+            }
+            if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
+                return;
+            }
+            removeStateMachine(device);
+        }
+    }
+
+    private void removeStateMachine(BluetoothDevice device) {
+        synchronized (mStateMachines) {
+            HearingAidStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                Log.w(TAG, "removeStateMachine: device " + device
+                        + " does not have a state machine");
+                return;
+            }
+            Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
+            sm.doQuit();
+            sm.cleanup();
+            mStateMachines.remove(device);
+        }
+    }
+
+    private List<BluetoothDevice> getConnectedPeerDevices(long hiSyncId) {
+        List<BluetoothDevice> result = new ArrayList<>();
+        for (BluetoothDevice peerDevice : getConnectedDevices()) {
+            if (getHiSyncId(peerDevice) == hiSyncId) {
+                result.add(peerDevice);
+            }
+        }
+        return result;
+    }
+
+    @VisibleForTesting
+    synchronized void connectionStateChanged(BluetoothDevice device, int fromState,
+                                                     int toState) {
+        if ((device == null) || (fromState == toState)) {
+            Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device
+                    + " fromState=" + fromState + " toState=" + toState);
+            return;
+        }
+        if (toState == BluetoothProfile.STATE_CONNECTED) {
+            long myHiSyncId = getHiSyncId(device);
+            if (myHiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID
+                    || getConnectedPeerDevices(myHiSyncId).size() == 1) {
+                // Log hearing aid connection event if we are the first device in a set
+                // Or when the hiSyncId has not been found
+                MetricsLogger.logProfileConnectionEvent(
+                        BluetoothMetricsProto.ProfileId.HEARING_AID);
+            }
+            setActiveDevice(device);
+        }
+        if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) {
+            setActiveDevice(null);
+        }
+        // Check if the device is disconnected - if unbond, remove the state machine
+        if (toState == BluetoothProfile.STATE_DISCONNECTED) {
+            int bondState = mAdapterService.getBondState(device);
+            if (bondState == BluetoothDevice.BOND_NONE) {
+                if (DBG) {
+                    Log.d(TAG, device + " is unbond. Remove state machine");
+                }
+                removeStateMachine(device);
+            }
+        }
+    }
+
+    private class ConnectionStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+            int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+            connectionStateChanged(device, fromState, toState);
+        }
+    }
+
+    /**
+     * Binder object: must be a static class or memory leak may occur
+     */
+    @VisibleForTesting
+    static class BluetoothHearingAidBinder extends IBluetoothHearingAid.Stub
+            implements IProfileServiceBinder {
+        private HearingAidService mService;
+
+        private HearingAidService getService() {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "HearingAid call not allowed for non-active user");
+                return null;
+            }
+
+            if (mService != null && mService.isAvailable()) {
+                return mService;
+            }
+            return null;
+        }
+
+        BluetoothHearingAidBinder(HearingAidService svc) {
+            mService = svc;
+        }
+
+        @Override
+        public void cleanup() {
+            mService = null;
+        }
+
+        @Override
+        public boolean connect(BluetoothDevice device) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.connect(device);
+        }
+
+        @Override
+        public boolean disconnect(BluetoothDevice device) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.disconnect(device);
+        }
+
+        @Override
+        public List<BluetoothDevice> getConnectedDevices() {
+            HearingAidService service = getService();
+            if (service == null) {
+                return new ArrayList<>();
+            }
+            return service.getConnectedDevices();
+        }
+
+        @Override
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return new ArrayList<>();
+            }
+            return service.getDevicesMatchingConnectionStates(states);
+        }
+
+        @Override
+        public int getConnectionState(BluetoothDevice device) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+            return service.getConnectionState(device);
+        }
+
+        @Override
+        public boolean setActiveDevice(BluetoothDevice device) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setActiveDevice(device);
+        }
+
+        @Override
+        public List<BluetoothDevice> getActiveDevices() {
+            HearingAidService service = getService();
+            if (service == null) {
+                return new ArrayList<>();
+            }
+            return service.getActiveDevices();
+        }
+
+        @Override
+        public boolean setPriority(BluetoothDevice device, int priority) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setPriority(device, priority);
+        }
+
+        @Override
+        public int getPriority(BluetoothDevice device) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
+            return service.getPriority(device);
+        }
+
+        @Override
+        public void setVolume(int volume) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return;
+            }
+            service.setVolume(volume);
+        }
+
+        @Override
+        public void adjustVolume(int direction) {
+            // TODO: Remove me?
+        }
+
+        @Override
+        public int getVolume() {
+            // TODO: Remove me?
+            return 0;
+        }
+
+        @Override
+        public long getHiSyncId(BluetoothDevice device) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return BluetoothHearingAid.HI_SYNC_ID_INVALID;
+            }
+            return service.getHiSyncId(device);
+        }
+
+        @Override
+        public int getDeviceSide(BluetoothDevice device) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return BluetoothHearingAid.SIDE_RIGHT;
+            }
+            return service.getCapabilities(device) & 1;
+        }
+
+        @Override
+        public int getDeviceMode(BluetoothDevice device) {
+            HearingAidService service = getService();
+            if (service == null) {
+                return BluetoothHearingAid.MODE_BINAURAL;
+            }
+            return service.getCapabilities(device) >> 1 & 1;
+        }
+    }
+
+    @Override
+    public void dump(StringBuilder sb) {
+        super.dump(sb);
+        for (HearingAidStateMachine sm : mStateMachines.values()) {
+            sm.dump(sb);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidStackEvent.java b/src/com/android/bluetooth/hearingaid/HearingAidStackEvent.java
new file mode 100644
index 0000000..932eb18
--- /dev/null
+++ b/src/com/android/bluetooth/hearingaid/HearingAidStackEvent.java
@@ -0,0 +1,71 @@
+/*
+ * 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.hearingaid;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * Stack event sent via a callback from JNI to Java, or generated
+ * internally by the Hearing Aid State Machine.
+ */
+public class HearingAidStackEvent {
+    // Event types for STACK_EVENT message (coming from native)
+    private static final int EVENT_TYPE_NONE = 0;
+    public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+    public static final int EVENT_TYPE_DEVICE_AVAILABLE = 2;
+
+    // Do not modify without updating the HAL bt_hearing_aid.h files.
+    // Match up with enum class ConnectionState of bt_hearing_aid.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;
+
+    public int type;
+    public BluetoothDevice device;
+    public int valueInt1;
+    public long valueLong2;
+
+    HearingAidStackEvent(int type) {
+        this.type = type;
+    }
+
+    @Override
+    public String toString() {
+        // event dump
+        StringBuilder result = new StringBuilder();
+        result.append("HearingAidStackEvent {type:" + eventTypeToString(type));
+        result.append(", device:" + device);
+        result.append(", value1:" + valueInt1);
+        result.append(", value2:" + valueLong2);
+        result.append("}");
+        return result.toString();
+    }
+
+    private static String eventTypeToString(int type) {
+        switch (type) {
+            case EVENT_TYPE_NONE:
+                return "EVENT_TYPE_NONE";
+            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                return "EVENT_TYPE_CONNECTION_STATE_CHANGED";
+            case EVENT_TYPE_DEVICE_AVAILABLE:
+                return "EVENT_TYPE_DEVICE_AVAILABLE";
+            default:
+                return "EVENT_TYPE_UNKNOWN:" + type;
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
new file mode 100644
index 0000000..3e0d617
--- /dev/null
+++ b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
@@ -0,0 +1,588 @@
+/*
+ * 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.
+ */
+
+/**
+ * Bluetooth HearingAid StateMachine. There is one instance per remote device.
+ *  - "Disconnected" and "Connected" are steady states.
+ *  - "Connecting" and "Disconnecting" are transient states until the
+ *     connection / disconnection is completed.
+ *
+ *
+ *                        (Disconnected)
+ *                           |       ^
+ *                   CONNECT |       | DISCONNECTED
+ *                           V       |
+ *                 (Connecting)<--->(Disconnecting)
+ *                           |       ^
+ *                 CONNECTED |       | DISCONNECT
+ *                           V       |
+ *                          (Connected)
+ * NOTES:
+ *  - If state machine is in "Connecting" state and the remote device sends
+ *    DISCONNECT request, the state machine transitions to "Disconnecting" state.
+ *  - Similarly, if the state machine is in "Disconnecting" state and the remote device
+ *    sends CONNECT request, the state machine transitions to "Connecting" state.
+ *
+ *                    DISCONNECT
+ *    (Connecting) ---------------> (Disconnecting)
+ *                 <---------------
+ *                      CONNECT
+ *
+ */
+
+package com.android.bluetooth.hearingaid;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+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.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Scanner;
+
+final class HearingAidStateMachine extends StateMachine {
+    private static final boolean DBG = false;
+    private static final String TAG = "HearingAidStateMachine";
+
+    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;
+
+    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;
+    private HearingAidNativeInterface mNativeInterface;
+
+    private final BluetoothDevice mDevice;
+
+    HearingAidStateMachine(BluetoothDevice device, HearingAidService svc,
+            HearingAidNativeInterface nativeInterface, Looper looper) {
+        super(TAG, looper);
+        mDevice = device;
+        mService = svc;
+        mNativeInterface = nativeInterface;
+
+        mDisconnected = new Disconnected();
+        mConnecting = new Connecting();
+        mDisconnecting = new Disconnecting();
+        mConnected = new Connected();
+
+        addState(mDisconnected);
+        addState(mConnecting);
+        addState(mDisconnecting);
+        addState(mConnected);
+
+        setInitialState(mDisconnected);
+    }
+
+    static HearingAidStateMachine make(BluetoothDevice device, HearingAidService svc,
+            HearingAidNativeInterface nativeInterface, Looper looper) {
+        Log.i(TAG, "make for device " + device);
+        HearingAidStateMachine HearingAidSm = new HearingAidStateMachine(device, svc,
+                nativeInterface, looper);
+        HearingAidSm.start();
+        return HearingAidSm;
+    }
+
+    public void doQuit() {
+        log("doQuit for device " + mDevice);
+        quitNow();
+    }
+
+    public void cleanup() {
+        log("cleanup for device " + mDevice);
+    }
+
+    @VisibleForTesting
+    class Disconnected extends State {
+        @Override
+        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);
+            }
+        }
+
+        @Override
+        public void exit() {
+            log("Exit Disconnected(" + mDevice + "): " + messageWhatToString(
+                    getCurrentMessage().what));
+            mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Disconnected process message(" + mDevice + "): " + messageWhatToString(
+                    message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    log("Connecting to " + mDevice);
+                    if (!mNativeInterface.connectHearingAid(mDevice)) {
+                        Log.e(TAG, "Disconnected: error connecting to " + mDevice);
+                        break;
+                    }
+                    if (mService.okToConnect(mDevice)) {
+                        transitionTo(mConnecting);
+                    } else {
+                        // Reject the request and stay in Disconnected state
+                        Log.w(TAG, "Outgoing HearingAid Connecting request rejected: " + mDevice);
+                    }
+                    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);
+                    }
+                    break;
+                case STACK_EVENT:
+                    HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
+                    if (DBG) {
+                        Log.d(TAG, "Disconnected: stack event: " + event);
+                    }
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt1);
+                            break;
+                        default:
+                            Log.e(TAG, "Disconnected: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Disconnected state
+        private void processConnectionEvent(int state) {
+            switch (state) {
+                case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.w(TAG, "Ignore HearingAid DISCONNECTED event: " + mDevice);
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_CONNECTING:
+                    if (mService.okToConnect(mDevice)) {
+                        Log.i(TAG, "Incoming HearingAid Connecting request accepted: " + mDevice);
+                        transitionTo(mConnecting);
+                    } else {
+                        // Reject the connection and stay in Disconnected state itself
+                        Log.w(TAG, "Incoming HearingAid Connecting request rejected: " + mDevice);
+                        mNativeInterface.disconnectHearingAid(mDevice);
+                    }
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_CONNECTED:
+                    Log.w(TAG, "HearingAid Connected from Disconnected state: " + mDevice);
+                    if (mService.okToConnect(mDevice)) {
+                        Log.i(TAG, "Incoming HearingAid Connected request accepted: " + mDevice);
+                        transitionTo(mConnected);
+                    } else {
+                        // Reject the connection and stay in Disconnected state itself
+                        Log.w(TAG, "Incoming HearingAid Connected request rejected: " + mDevice);
+                        mNativeInterface.disconnectHearingAid(mDevice);
+                    }
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    Log.w(TAG, "Ignore HearingAid DISCONNECTING event: " + mDevice);
+                    break;
+                default:
+                    Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice);
+                    break;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    class Connecting extends State {
+        @Override
+        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);
+        }
+
+        @Override
+        public void exit() {
+            log("Exit Connecting(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
+            removeMessages(CONNECT_TIMEOUT);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Connecting process message(" + mDevice + "): "
+                    + messageWhatToString(message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    deferMessage(message);
+                    break;
+                case CONNECT_TIMEOUT:
+                    Log.w(TAG, "Connecting connection timeout: " + mDevice + ". Try whitelist");
+                    mNativeInterface.disconnectHearingAid(mDevice);
+                    mNativeInterface.addToWhiteList(mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case CHECK_WHITELIST_CONNECTION:
+                    deferMessage(message);
+                    break;
+                case DISCONNECT:
+                    log("Connecting: connection canceled to " + mDevice);
+                    mNativeInterface.disconnectHearingAid(mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case STACK_EVENT:
+                    HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
+                    log("Connecting: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt1);
+                            break;
+                        default:
+                            Log.e(TAG, "Connecting: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Connecting state
+        private void processConnectionEvent(int state) {
+            switch (state) {
+                case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.w(TAG, "Connecting device disconnected: " + mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_CONNECTED:
+                    transitionTo(mConnected);
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_CONNECTING:
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice);
+                    transitionTo(mDisconnecting);
+                    break;
+                default:
+                    Log.e(TAG, "Incorrect state: " + state);
+                    break;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    class Disconnecting extends State {
+        @Override
+        public void enter() {
+            Log.i(TAG, "Enter Disconnecting(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            sendMessageDelayed(CONNECT_TIMEOUT, sDisconnectTimeoutMs);
+            mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+            broadcastConnectionState(mConnectionState, mLastConnectionState);
+        }
+
+        @Override
+        public void exit() {
+            log("Exit Disconnecting(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+            removeMessages(CONNECT_TIMEOUT);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Disconnecting process message(" + mDevice + "): "
+                    + messageWhatToString(message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    deferMessage(message);
+                    break;
+                case CONNECT_TIMEOUT: {
+                    Log.w(TAG, "Disconnecting connection timeout: " + mDevice);
+                    mNativeInterface.disconnectHearingAid(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:
+                    deferMessage(message);
+                    break;
+                case STACK_EVENT:
+                    HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
+                    log("Disconnecting: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt1);
+                            break;
+                        default:
+                            Log.e(TAG, "Disconnecting: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Disconnecting state
+        private void processConnectionEvent(int state) {
+            switch (state) {
+                case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.i(TAG, "Disconnected: " + mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_CONNECTED:
+                    if (mService.okToConnect(mDevice)) {
+                        Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice);
+                        transitionTo(mConnected);
+                    } else {
+                        // Reject the connection and stay in Disconnecting state
+                        Log.w(TAG, "Incoming HearingAid Connected request rejected: " + mDevice);
+                        mNativeInterface.disconnectHearingAid(mDevice);
+                    }
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_CONNECTING:
+                    if (mService.okToConnect(mDevice)) {
+                        Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice);
+                        transitionTo(mConnecting);
+                    } else {
+                        // Reject the connection and stay in Disconnecting state
+                        Log.w(TAG, "Incoming HearingAid Connecting request rejected: " + mDevice);
+                        mNativeInterface.disconnectHearingAid(mDevice);
+                    }
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    break;
+                default:
+                    Log.e(TAG, "Incorrect state: " + state);
+                    break;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    class Connected extends State {
+        @Override
+        public void enter() {
+            Log.i(TAG, "Enter Connected(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            mConnectionState = BluetoothProfile.STATE_CONNECTED;
+            removeDeferredMessages(CONNECT);
+            broadcastConnectionState(mConnectionState, mLastConnectionState);
+        }
+
+        @Override
+        public void exit() {
+            log("Exit Connected(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            mLastConnectionState = BluetoothProfile.STATE_CONNECTED;
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Connected process message(" + mDevice + "): "
+                    + messageWhatToString(message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    Log.w(TAG, "Connected: CONNECT ignored: " + mDevice);
+                    break;
+                case DISCONNECT:
+                    log("Disconnecting from " + mDevice);
+                    if (!mNativeInterface.disconnectHearingAid(mDevice)) {
+                        // If error in the native stack, transition directly to Disconnected state.
+                        Log.e(TAG, "Connected: error disconnecting from " + mDevice);
+                        transitionTo(mDisconnected);
+                        break;
+                    }
+                    transitionTo(mDisconnecting);
+                    break;
+                case STACK_EVENT:
+                    HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
+                    log("Connected: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt1);
+                            break;
+                        default:
+                            Log.e(TAG, "Connected: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Connected state
+        private void processConnectionEvent(int state) {
+            switch (state) {
+                case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.i(TAG, "Disconnected from " + mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    Log.i(TAG, "Disconnecting from " + mDevice);
+                    transitionTo(mDisconnecting);
+                    break;
+                default:
+                    Log.e(TAG, "Connection State Device: " + mDevice + " bad state: " + state);
+                    break;
+            }
+        }
+    }
+
+    int getConnectionState() {
+        return mConnectionState;
+    }
+
+    BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    synchronized boolean isConnected() {
+        return getCurrentState() == mConnected;
+    }
+
+    // This method does not check for error condition (newState == prevState)
+    private void broadcastConnectionState(int newState, int prevState) {
+        log("Connection state " + mDevice + ": " + profileStateToString(prevState)
+                    + "->" + profileStateToString(newState));
+
+        Intent intent = new Intent(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+    }
+
+    private static String messageWhatToString(int what) {
+        switch (what) {
+            case CONNECT:
+                return "CONNECT";
+            case DISCONNECT:
+                return "DISCONNECT";
+            case STACK_EVENT:
+                return "STACK_EVENT";
+            case CONNECT_TIMEOUT:
+                return "CONNECT_TIMEOUT";
+            default:
+                break;
+        }
+        return Integer.toString(what);
+    }
+
+    private static String profileStateToString(int state) {
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return "DISCONNECTED";
+            case BluetoothProfile.STATE_CONNECTING:
+                return "CONNECTING";
+            case BluetoothProfile.STATE_CONNECTED:
+                return "CONNECTED";
+            case BluetoothProfile.STATE_DISCONNECTING:
+                return "DISCONNECTING";
+            default:
+                break;
+        }
+        return Integer.toString(state);
+    }
+
+    public void dump(StringBuilder sb) {
+        ProfileService.println(sb, "mDevice: " + mDevice);
+        ProfileService.println(sb, "  StateMachine: " + this);
+        // Dump the state machine logs
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+        super.dump(new FileDescriptor(), printWriter, new String[]{});
+        printWriter.flush();
+        stringWriter.flush();
+        ProfileService.println(sb, "  StateMachineLog:");
+        Scanner scanner = new Scanner(stringWriter.toString());
+        while (scanner.hasNextLine()) {
+            String line = scanner.nextLine();
+            ProfileService.println(sb, "    " + line);
+        }
+        scanner.close();
+    }
+
+    @Override
+    protected void log(String msg) {
+        if (DBG) {
+            super.log(msg);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/AtPhonebook.java b/src/com/android/bluetooth/hfp/AtPhonebook.java
old mode 100755
new mode 100644
index 0387394..937e767
--- a/src/com/android/bluetooth/hfp/AtPhonebook.java
+++ b/src/com/android/bluetooth/hfp/AtPhonebook.java
@@ -16,9 +16,6 @@
 
 package com.android.bluetooth.hfp;
 
-import com.android.bluetooth.R;
-import com.android.internal.telephony.GsmAlphabet;
-
 import android.bluetooth.BluetoothDevice;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -34,6 +31,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.util.DevicePolicyUtils;
+import com.android.internal.telephony.GsmAlphabet;
 
 import java.util.HashMap;
 
@@ -49,15 +47,15 @@
      *  to AT+CPBR for the MC, RC, and DC phone books (missed, received, and
      *   dialed calls respectively)
      */
-    private static final String[] CALLS_PROJECTION = new String[] {
-        Calls._ID, Calls.NUMBER, Calls.NUMBER_PRESENTATION
+    private static final String[] CALLS_PROJECTION = new String[]{
+            Calls._ID, Calls.NUMBER, Calls.NUMBER_PRESENTATION
     };
 
     /** The projection to use when querying the contacts database in response
      *   to AT+CPBR for the ME phonebook (saved phone numbers).
      */
-    private static final String[] PHONES_PROJECTION = new String[] {
-        Phone._ID, Phone.DISPLAY_NAME, Phone.NUMBER, Phone.TYPE
+    private static final String[] PHONES_PROJECTION = new String[]{
+            Phone._ID, Phone.DISPLAY_NAME, Phone.NUMBER, Phone.TYPE
     };
 
     /** Android supports as many phonebook entries as the flash can hold, but
@@ -69,16 +67,16 @@
     private static final String MISSED_CALL_WHERE = Calls.TYPE + "=" + Calls.MISSED_TYPE;
 
     private class PhonebookResult {
-        public Cursor  cursor; // result set of last query
-        public int     numberColumn;
-        public int     numberPresentationColumn;
-        public int     typeColumn;
-        public int     nameColumn;
-    };
+        public Cursor cursor; // result set of last query
+        public int numberColumn;
+        public int numberPresentationColumn;
+        public int typeColumn;
+        public int nameColumn;
+    }
 
     private Context mContext;
     private ContentResolver mContentResolver;
-    private HeadsetStateMachine mStateMachine;
+    private HeadsetNativeInterface mNativeInterface;
     private String mCurrentPhonebook;
     private String mCharacterSet = "UTF-8";
 
@@ -92,25 +90,22 @@
     private final HashMap<String, PhonebookResult> mPhonebooks =
             new HashMap<String, PhonebookResult>(4);
 
-    final int TYPE_UNKNOWN = -1;
-    final int TYPE_READ = 0;
-    final int TYPE_SET = 1;
-    final int TYPE_TEST = 2;
+    static final int TYPE_UNKNOWN = -1;
+    static final int TYPE_READ = 0;
+    static final int TYPE_SET = 1;
+    static final int TYPE_TEST = 2;
 
-    public AtPhonebook(Context context, HeadsetStateMachine headsetState) {
+    public AtPhonebook(Context context, HeadsetNativeInterface nativeInterface) {
         mContext = context;
         mPairingPackage = context.getString(R.string.pairing_ui_package);
         mContentResolver = context.getContentResolver();
-        mStateMachine = headsetState;
+        mNativeInterface = nativeInterface;
         mPhonebooks.put("DC", new PhonebookResult());  // dialled calls
         mPhonebooks.put("RC", new PhonebookResult());  // received calls
         mPhonebooks.put("MC", new PhonebookResult());  // missed calls
         mPhonebooks.put("ME", new PhonebookResult());  // mobile phonebook
-
         mCurrentPhonebook = "ME";  // default to mobile phonebook
-
         mCpbrIndex1 = mCpbrIndex2 = -1;
-        mCheckingAccessPermission = false;
     }
 
     public void cleanup() {
@@ -121,12 +116,16 @@
     public String getLastDialledNumber() {
         String[] projection = {Calls.NUMBER};
         Cursor cursor = mContentResolver.query(Calls.CONTENT_URI, projection,
-                Calls.TYPE + "=" + Calls.OUTGOING_TYPE, null, Calls.DEFAULT_SORT_ORDER +
-                " LIMIT 1");
-        if (cursor == null) return null;
+                Calls.TYPE + "=" + Calls.OUTGOING_TYPE, null,
+                Calls.DEFAULT_SORT_ORDER + " LIMIT 1");
+        if (cursor == null) {
+            Log.w(TAG, "getLastDialledNumber, cursor is null");
+            return null;
+        }
 
         if (cursor.getCount() < 1) {
             cursor.close();
+            Log.w(TAG, "getLastDialledNumber, cursor.getCount is 0");
             return null;
         }
         cursor.moveToNext();
@@ -152,9 +151,8 @@
         return Utils.getBytesFromAddress(device.getAddress());
     }
 
-    public void handleCscsCommand(String atString, int type, BluetoothDevice device)
-    {
-        log("handleCscsCommand - atString = " +atString);
+    public void handleCscsCommand(String atString, int type, BluetoothDevice device) {
+        log("handleCscsCommand - atString = " + atString);
         // Select Character Set
         int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
         int atCommandErrorCode = -1;
@@ -167,21 +165,20 @@
                 break;
             case TYPE_TEST: // Test
                 log("handleCscsCommand - Test Command");
-                atCommandResponse = ( "+CSCS: (\"UTF-8\",\"IRA\",\"GSM\")");
+                atCommandResponse = ("+CSCS: (\"UTF-8\",\"IRA\",\"GSM\")");
                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
                 break;
             case TYPE_SET: // Set
                 log("handleCscsCommand - Set Command");
                 String[] args = atString.split("=");
-                if (args.length < 2 || !(args[1] instanceof String)) {
-                    mStateMachine.atResponseCodeNative(atCommandResult,
-                           atCommandErrorCode, getByteAddress(device));
+                if (args.length < 2 || args[1] == null) {
+                    mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode);
                     break;
                 }
                 String characterSet = ((atString.split("="))[1]);
                 characterSet = characterSet.replace("\"", "");
-                if (characterSet.equals("GSM") || characterSet.equals("IRA") ||
-                    characterSet.equals("UTF-8") || characterSet.equals("UTF8")) {
+                if (characterSet.equals("GSM") || characterSet.equals("IRA") || characterSet.equals(
+                        "UTF-8") || characterSet.equals("UTF8")) {
                     mCharacterSet = characterSet;
                     atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
                 } else {
@@ -193,15 +190,15 @@
                 log("handleCscsCommand - Invalid chars");
                 atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
         }
-        if (atCommandResponse != null)
-            mStateMachine.atResponseStringNative(atCommandResponse, getByteAddress(device));
-        mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
-                                         getByteAddress(device));
+        if (atCommandResponse != null) {
+            mNativeInterface.atResponseString(device, atCommandResponse);
+        }
+        mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode);
     }
 
     public void handleCpbsCommand(String atString, int type, BluetoothDevice device) {
         // Select PhoneBook memory Storage
-        log("handleCpbsCommand - atString = " +atString);
+        log("handleCpbsCommand - atString = " + atString);
         int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
         int atCommandErrorCode = -1;
         String atCommandResponse = null;
@@ -220,7 +217,9 @@
                     break;
                 }
                 int size = pbr.cursor.getCount();
-                atCommandResponse = "+CPBS: \"" + mCurrentPhonebook + "\"," + size + "," + getMaxPhoneBookSize(size);
+                atCommandResponse =
+                        "+CPBS: \"" + mCurrentPhonebook + "\"," + size + "," + getMaxPhoneBookSize(
+                                size);
                 pbr.cursor.close();
                 pbr.cursor = null;
                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
@@ -234,17 +233,23 @@
                 log("handleCpbsCommand - set command");
                 String[] args = atString.split("=");
                 // Select phonebook memory
-                if (args.length < 2 || !(args[1] instanceof String)) {
+                if (args.length < 2 || args[1] == null) {
                     atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_SUPPORTED;
                     break;
                 }
                 String pb = args[1].trim();
-                while (pb.endsWith("\"")) pb = pb.substring(0, pb.length() - 1);
-                while (pb.startsWith("\"")) pb = pb.substring(1, pb.length());
+                while (pb.endsWith("\"")) {
+                    pb = pb.substring(0, pb.length() - 1);
+                }
+                while (pb.startsWith("\"")) {
+                    pb = pb.substring(1, pb.length());
+                }
                 if (getPhonebookResult(pb, false) == null && !"SM".equals(pb)) {
-                   if (DBG) log("Dont know phonebook: '" + pb + "'");
-                   atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
-                   break;
+                    if (DBG) {
+                        log("Dont know phonebook: '" + pb + "'");
+                    }
+                    atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
+                    break;
                 }
                 mCurrentPhonebook = pb;
                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
@@ -254,14 +259,14 @@
                 log("handleCpbsCommand - invalid chars");
                 atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
         }
-        if (atCommandResponse != null)
-            mStateMachine.atResponseStringNative(atCommandResponse, getByteAddress(device));
-        mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
-                                             getByteAddress(device));
+        if (atCommandResponse != null) {
+            mNativeInterface.atResponseString(device, atCommandResponse);
+        }
+        mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode);
     }
 
-    public void handleCpbrCommand(String atString, int type, BluetoothDevice remoteDevice) {
-        log("handleCpbrCommand - atString = " +atString);
+    void handleCpbrCommand(String atString, int type, BluetoothDevice remoteDevice) {
+        log("handleCpbrCommand - atString = " + atString);
         int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
         int atCommandErrorCode = -1;
         String atCommandResponse = null;
@@ -280,12 +285,12 @@
                     PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
                     if (pbr == null) {
                         atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
-                        mStateMachine.atResponseCodeNative(atCommandResult,
-                           atCommandErrorCode, getByteAddress(remoteDevice));
+                        mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
+                                atCommandErrorCode);
                         break;
                     }
                     size = pbr.cursor.getCount();
-                    log("handleCpbrCommand - size = "+size);
+                    log("handleCpbrCommand - size = " + size);
                     pbr.cursor.close();
                     pbr.cursor = null;
                 }
@@ -295,11 +300,8 @@
                 }
                 atCommandResponse = "+CPBR: (1-" + size + "),30,30";
                 atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
-                if (atCommandResponse != null)
-                    mStateMachine.atResponseStringNative(atCommandResponse,
-                                         getByteAddress(remoteDevice));
-                mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
-                                         getByteAddress(remoteDevice));
+                mNativeInterface.atResponseString(remoteDevice, atCommandResponse);
+                mNativeInterface.atResponseCode(remoteDevice, atCommandResult, atCommandErrorCode);
                 break;
             // Read PhoneBook Entries
             case TYPE_READ:
@@ -309,36 +311,37 @@
                 log("handleCpbrCommand - set/read command");
                 if (mCpbrIndex1 != -1) {
                    /* handling a CPBR at the moment, reject this CPBR command */
-                   atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
-                   mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
-                                         getByteAddress(remoteDevice));
-                   break;
+                    atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
+                    mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
+                            atCommandErrorCode);
+                    break;
                 }
                 // Parse indexes
                 int index1;
                 int index2;
                 if ((atString.split("=")).length < 2) {
-                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
-                                         getByteAddress(remoteDevice));
+                    mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
+                            atCommandErrorCode);
                     break;
                 }
                 String atCommand = (atString.split("="))[1];
                 String[] indices = atCommand.split(",");
-                for(int i = 0; i < indices.length; i++)
-                    //replace AT command separator ';' from the index if any
+                //replace AT command separator ';' from the index if any
+                for (int i = 0; i < indices.length; i++) {
                     indices[i] = indices[i].replace(';', ' ').trim();
+                }
                 try {
                     index1 = Integer.parseInt(indices[0]);
-                    if (indices.length == 1)
+                    if (indices.length == 1) {
                         index2 = index1;
-                    else
+                    } else {
                         index2 = Integer.parseInt(indices[1]);
-                }
-                catch (Exception e) {
+                    }
+                } catch (Exception e) {
                     log("handleCpbrCommand - exception - invalid chars: " + e.toString());
                     atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
-                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
-                                         getByteAddress(remoteDevice));
+                    mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
+                            atCommandErrorCode);
                     break;
                 }
                 mCpbrIndex1 = index1;
@@ -350,14 +353,14 @@
                     mCheckingAccessPermission = false;
                     atCommandResult = processCpbrCommand(remoteDevice);
                     mCpbrIndex1 = mCpbrIndex2 = -1;
-                    mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
-                                         getByteAddress(remoteDevice));
+                    mNativeInterface.atResponseCode(remoteDevice, atCommandResult,
+                            atCommandErrorCode);
                     break;
                 } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
                     mCheckingAccessPermission = false;
                     mCpbrIndex1 = mCpbrIndex2 = -1;
-                    mStateMachine.atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR,
-                            BluetoothCmeError.AG_FAILURE, getByteAddress(remoteDevice));
+                    mNativeInterface.atResponseCode(remoteDevice,
+                            HeadsetHalConstants.AT_RESPONSE_ERROR, BluetoothCmeError.AG_FAILURE);
                 }
                 // If checkAccessPermission(remoteDevice) has returned
                 // BluetoothDevice.ACCESS_UNKNOWN, we will continue the process in
@@ -368,8 +371,7 @@
             default:
                 log("handleCpbrCommand - invalid chars");
                 atCommandErrorCode = BluetoothCmeError.TEXT_HAS_INVALID_CHARS;
-                mStateMachine.atResponseCodeNative(atCommandResult, atCommandErrorCode,
-                        getByteAddress(remoteDevice));
+                mNativeInterface.atResponseCode(remoteDevice, atCommandResult, atCommandErrorCode);
         }
     }
 
@@ -418,10 +420,11 @@
         }
 
         if (ancillaryPhonebook) {
-            pbr.cursor = mContentResolver.query(
-                    Calls.CONTENT_URI, CALLS_PROJECTION, where, null,
+            pbr.cursor = mContentResolver.query(Calls.CONTENT_URI, CALLS_PROJECTION, where, null,
                     Calls.DEFAULT_SORT_ORDER + " LIMIT " + MAX_PHONEBOOK_SIZE);
-            if (pbr.cursor == null) return false;
+            if (pbr.cursor == null) {
+                return false;
+            }
 
             pbr.numberColumn = pbr.cursor.getColumnIndexOrThrow(Calls.NUMBER);
             pbr.numberPresentationColumn =
@@ -430,9 +433,11 @@
             pbr.nameColumn = -1;
         } else {
             final Uri phoneContentUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
-            pbr.cursor = mContentResolver.query(phoneContentUri, PHONES_PROJECTION,
-                    where, null, Phone.NUMBER + " LIMIT " + MAX_PHONEBOOK_SIZE);
-            if (pbr.cursor == null) return false;
+            pbr.cursor = mContentResolver.query(phoneContentUri, PHONES_PROJECTION, where, null,
+                    Phone.NUMBER + " LIMIT " + MAX_PHONEBOOK_SIZE);
+            if (pbr.cursor == null) {
+                return false;
+            }
 
             pbr.numberColumn = pbr.cursor.getColumnIndex(Phone.NUMBER);
             pbr.numberPresentationColumn = -1;
@@ -472,8 +477,7 @@
     }
 
     // process CPBR command after permission check
-    /*package*/ int processCpbrCommand(BluetoothDevice device)
-    {
+    /*package*/ int processCpbrCommand(BluetoothDevice device) {
         log("processCpbrCommand");
         int atCommandResult = HeadsetHalConstants.AT_RESPONSE_ERROR;
         int atCommandErrorCode = -1;
@@ -490,6 +494,7 @@
         // Check phonebook
         PhonebookResult pbr = getPhonebookResult(mCurrentPhonebook, true); //false);
         if (pbr == null) {
+            Log.e(TAG, "pbr is null");
             atCommandErrorCode = BluetoothCmeError.OPERATION_NOT_ALLOWED;
             return atCommandResult;
         }
@@ -498,17 +503,23 @@
         // Send OK instead of ERROR if these checks fail.
         // When we send error, certain kits like BMW disconnect the
         // Handsfree connection.
-        if (pbr.cursor.getCount() == 0 || mCpbrIndex1 <= 0 || mCpbrIndex2 < mCpbrIndex1  ||
-            mCpbrIndex2 > pbr.cursor.getCount() || mCpbrIndex1 > pbr.cursor.getCount()) {
+        if (pbr.cursor.getCount() == 0 || mCpbrIndex1 <= 0 || mCpbrIndex2 < mCpbrIndex1
+                || mCpbrIndex1 > pbr.cursor.getCount()) {
             atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
+            Log.e(TAG, "Invalid request or no results, returning");
             return atCommandResult;
         }
 
+        if (mCpbrIndex2 > pbr.cursor.getCount()) {
+            Log.w(TAG, "max index requested is greater than number of records"
+                    + " available, resetting it");
+            mCpbrIndex2 = pbr.cursor.getCount();
+        }
         // Process
         atCommandResult = HeadsetHalConstants.AT_RESPONSE_OK;
         int errorDetected = -1; // no error
         pbr.cursor.moveToPosition(mCpbrIndex1 - 1);
-        log("mCpbrIndex1 = "+mCpbrIndex1+ " and mCpbrIndex2 = "+mCpbrIndex2);
+        log("mCpbrIndex1 = " + mCpbrIndex1 + " and mCpbrIndex2 = " + mCpbrIndex2);
         for (int index = mCpbrIndex1; index <= mCpbrIndex2; index++) {
             String number = pbr.cursor.getString(pbr.numberColumn);
             String name = null;
@@ -519,7 +530,7 @@
                 // take 7 seconds to process 100 missed calls.
                 Cursor c = mContentResolver.query(
                         Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, number),
-                        new String[] {
+                        new String[]{
                                 PhoneLookup.DISPLAY_NAME, PhoneLookup.TYPE
                         }, null, null, null);
                 if (c != null) {
@@ -529,28 +540,38 @@
                     }
                     c.close();
                 }
-                if (DBG && name == null) log("Caller ID lookup failed for " + number);
+                if (DBG && name == null) {
+                    log("Caller ID lookup failed for " + number);
+                }
 
             } else if (pbr.nameColumn != -1) {
                 name = pbr.cursor.getString(pbr.nameColumn);
             } else {
                 log("processCpbrCommand: empty name and number");
             }
-            if (name == null) name = "";
+            if (name == null) {
+                name = "";
+            }
             name = name.trim();
-            if (name.length() > 28) name = name.substring(0, 28);
+            if (name.length() > 28) {
+                name = name.substring(0, 28);
+            }
 
             if (pbr.typeColumn != -1) {
                 type = pbr.cursor.getInt(pbr.typeColumn);
                 name = name + "/" + getPhoneType(type);
             }
 
-            if (number == null) number = "";
+            if (number == null) {
+                number = "";
+            }
             int regionType = PhoneNumberUtils.toaFromString(number);
 
             number = number.trim();
             number = PhoneNumberUtils.stripSeparators(number);
-            if (number.length() > 30) number = number.substring(0, 30);
+            if (number.length() > 30) {
+                number = number.substring(0, 30);
+            }
             int numberPresentation = Calls.PRESENTATION_ALLOWED;
             if (pbr.numberPresentationColumn != -1) {
                 numberPresentation = pbr.cursor.getInt(pbr.numberPresentationColumn);
@@ -564,7 +585,7 @@
 
             // TODO(): Handle IRA commands. It's basically
             // a 7 bit ASCII character set.
-            if (!name.equals("") && mCharacterSet.equals("GSM")) {
+            if (!name.isEmpty() && mCharacterSet.equals("GSM")) {
                 byte[] nameByte = GsmAlphabet.stringToGsm8BitPacked(name);
                 if (nameByte == null) {
                     name = mContext.getString(R.string.unknownNumber);
@@ -576,12 +597,12 @@
             record = "+CPBR: " + index + ",\"" + number + "\"," + regionType + ",\"" + name + "\"";
             record = record + "\r\n\r\n";
             atCommandResponse = record;
-            mStateMachine.atResponseStringNative(atCommandResponse, getByteAddress(device));
+            mNativeInterface.atResponseString(device, atCommandResponse);
             if (!pbr.cursor.moveToNext()) {
                 break;
             }
         }
-        if(pbr != null && pbr.cursor != null) {
+        if (pbr.cursor != null) {
             pbr.cursor.close();
             pbr.cursor = null;
         }
@@ -605,7 +626,7 @@
             Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
             intent.setPackage(mPairingPackage);
             intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-            BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
+                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, remoteDevice);
             // Leave EXTRA_PACKAGE_NAME and EXTRA_CLASS_NAME field empty.
             // BluetoothHandsfree's broadcast receiver is anonymous, cannot be targeted.
diff --git a/src/com/android/bluetooth/hfp/HeadsetAgIndicatorEnableState.java b/src/com/android/bluetooth/hfp/HeadsetAgIndicatorEnableState.java
new file mode 100644
index 0000000..e39c6d9
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetAgIndicatorEnableState.java
@@ -0,0 +1,71 @@
+/*
+ * 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.hfp;
+
+/**
+ * An object that represents AG indicator enable states
+ */
+public class HeadsetAgIndicatorEnableState extends HeadsetMessageObject {
+    public boolean service;
+    public boolean roam;
+    public boolean signal;
+    public boolean battery;
+
+    HeadsetAgIndicatorEnableState(boolean newService, boolean newRoam, boolean newSignal,
+            boolean newBattery) {
+        service = newService;
+        roam = newRoam;
+        signal = newSignal;
+        battery = newBattery;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof HeadsetAgIndicatorEnableState)) {
+            return false;
+        }
+        HeadsetAgIndicatorEnableState other = (HeadsetAgIndicatorEnableState) obj;
+        return service == other.service && roam == other.roam && signal == other.signal
+                && battery == other.battery;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        if (service) result += 1;
+        if (roam) result += 2;
+        if (signal) result += 4;
+        if (battery) result += 8;
+        return result;
+    }
+
+    @Override
+    public void buildString(StringBuilder builder) {
+        if (builder == null) {
+            return;
+        }
+        builder.append(this.getClass().getSimpleName())
+                .append("[service=")
+                .append(service)
+                .append(", roam=")
+                .append(roam)
+                .append(", signal=")
+                .append(signal)
+                .append(", battery=")
+                .append(battery)
+                .append("]");
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetCallState.java b/src/com/android/bluetooth/hfp/HeadsetCallState.java
new file mode 100644
index 0000000..3afd3c4
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetCallState.java
@@ -0,0 +1,93 @@
+/*
+ * 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.hfp;
+
+import java.util.Objects;
+
+/**
+ * A blob of data representing an overall call state on the phone
+ */
+class HeadsetCallState extends HeadsetMessageObject {
+    /**
+     * Number of active calls
+     */
+    int mNumActive;
+    /**
+     * Number of held calls
+     */
+    int mNumHeld;
+    /**
+     * Current call setup state as defined in bthf_call_state_t of bt_hf.h or
+     * {@link com.android.server.telecom.BluetoothPhoneServiceImpl} or {@link HeadsetHalConstants}
+     */
+    int mCallState;
+    /**
+     * Currently active call's phone number
+     */
+    String mNumber;
+    /**
+     * Phone number type
+     */
+    int mType;
+
+    HeadsetCallState(int numActive, int numHeld, int callState, String number, int type) {
+        mNumActive = numActive;
+        mNumHeld = numHeld;
+        mCallState = callState;
+        mNumber = number;
+        mType = type;
+    }
+
+    @Override
+    public void buildString(StringBuilder builder) {
+        if (builder == null) {
+            return;
+        }
+        builder.append(this.getClass().getSimpleName())
+                .append("[numActive=")
+                .append(mNumActive)
+                .append(", numHeld=")
+                .append(mNumHeld)
+                .append(", callState=")
+                .append(mCallState)
+                .append(", number=");
+        if (mNumber == null) {
+            builder.append("null");
+        } else {
+            builder.append("***");
+        }
+        builder.append(mNumber).append(", type=").append(mType).append("]");
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof HeadsetCallState)) {
+            return false;
+        }
+        HeadsetCallState that = (HeadsetCallState) object;
+        return mNumActive == that.mNumActive && mNumHeld == that.mNumHeld
+                && mCallState == that.mCallState && Objects.equals(mNumber, that.mNumber)
+                && mType == that.mType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mNumActive, mNumHeld, mCallState, mNumber, mType);
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetClccResponse.java b/src/com/android/bluetooth/hfp/HeadsetClccResponse.java
new file mode 100644
index 0000000..f34fb34
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetClccResponse.java
@@ -0,0 +1,102 @@
+/*
+ * 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.hfp;
+
+/**
+ * A blob of response data to AT+CLCC command from HF
+ *
+ * Example:
+ *   AT+CLCC
+ *   +CLCC:[index],[direction],[status],[mode],[mpty][,[number],[type]]
+ */
+class HeadsetClccResponse extends HeadsetMessageObject {
+    /**
+     * Index of the call, starting with 1, by the sequence of setup or receiving the calls
+     */
+    int mIndex;
+    /**
+     * Direction of the call, 0 is outgoing, 1 is incoming
+     */
+    int mDirection;
+    /**
+     * Status of the call, currently defined in bthf_call_state_t of bt_hf.h or
+     * {@link com.android.server.telecom.BluetoothPhoneServiceImpl} or {@link HeadsetHalConstants}
+     *
+     * 0 - Active
+     * 1 - Held
+     * 2 - Dialing
+     * 3 - Alerting
+     * 4 - Incoming
+     * 5 - Waiting
+     * 6 - Call held by response and hold
+     */
+    int mStatus;
+    /**
+     * Call mode, 0 is Voice, 1 is Data, 2 is Fax
+     */
+    int mMode;
+    /**
+     * Multi-party indicator
+     *
+     * 0 - this call is NOT a member of a multi-party (conference) call
+     * 1 - this call IS a multi-party (conference) call
+     */
+    boolean mMpty;
+    /**
+     * Phone number of the call (optional)
+     */
+    String mNumber;
+    /**
+     * Phone number type (optional)
+     */
+    int mType;
+
+    HeadsetClccResponse(int index, int direction, int status, int mode, boolean mpty, String number,
+            int type) {
+        mIndex = index;
+        mDirection = direction;
+        mStatus = status;
+        mMode = mode;
+        mMpty = mpty;
+        mNumber = number;
+        mType = type;
+    }
+
+    @Override
+    public void buildString(StringBuilder builder) {
+        if (builder == null) {
+            return;
+        }
+        builder.append(this.getClass().getSimpleName())
+                .append("[index=")
+                .append(mIndex)
+                .append(", direction=")
+                .append(mDirection)
+                .append(", status=")
+                .append(mStatus)
+                .append(", callMode=")
+                .append(mMode)
+                .append(", isMultiParty=")
+                .append(mMpty)
+                .append(", number=");
+        if (mNumber == null) {
+            builder.append("null");
+        } else {
+            builder.append("***");
+        }
+        builder.append(", type=").append(mType).append("]");
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetDeviceState.java b/src/com/android/bluetooth/hfp/HeadsetDeviceState.java
new file mode 100644
index 0000000..ed47fc7
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetDeviceState.java
@@ -0,0 +1,68 @@
+/*
+ * 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.hfp;
+
+/**
+ * A blob of data representing AG's device state in response to an AT+CIND command from HF
+ */
+class HeadsetDeviceState extends HeadsetMessageObject {
+    /**
+     * Service availability indicator
+     *
+     * 0 - no service, no home/roam network is available
+     * 1 - presence of service, home/roam network available
+     */
+    int mService;
+    /**
+     * Roaming status indicator
+     *
+     * 0 - roaming is not active
+     * 1 - roaming is active
+     */
+    int mRoam;
+    /**
+     * Signal strength indicator, value ranges from 0 to 5
+     */
+    int mSignal;
+    /**
+     * Battery charge indicator from AG, value ranges from 0 to 5
+     */
+    int mBatteryCharge;
+
+    HeadsetDeviceState(int service, int roam, int signal, int batteryCharge) {
+        mService = service;
+        mRoam = roam;
+        mSignal = signal;
+        mBatteryCharge = batteryCharge;
+    }
+
+    @Override
+    public void buildString(StringBuilder builder) {
+        if (builder == null) {
+            return;
+        }
+        builder.append(this.getClass().getSimpleName())
+                .append("[hasCellularService=")
+                .append(mService)
+                .append(", isRoaming=")
+                .append(mRoam)
+                .append(", signalStrength")
+                .append(mSignal)
+                .append(", batteryCharge=")
+                .append(mBatteryCharge)
+                .append("]");
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetHalConstants.java b/src/com/android/bluetooth/hfp/HeadsetHalConstants.java
index 76a719a..23cb261 100644
--- a/src/com/android/bluetooth/hfp/HeadsetHalConstants.java
+++ b/src/com/android/bluetooth/hfp/HeadsetHalConstants.java
@@ -20,52 +20,58 @@
  * @hide
  */
 
-final public class HeadsetHalConstants {
+public final class HeadsetHalConstants {
     // Do not modify without upating the HAL bt_hf.h files.
 
     // match up with bthf_connection_state_t enum of bt_hf.h
-    final static int CONNECTION_STATE_DISCONNECTED = 0;
-    final static int CONNECTION_STATE_CONNECTING = 1;
-    final static int CONNECTION_STATE_CONNECTED = 2;
-    final static int CONNECTION_STATE_SLC_CONNECTED = 3;
-    final static int CONNECTION_STATE_DISCONNECTING = 4;
+    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_SLC_CONNECTED = 3;
+    static final int CONNECTION_STATE_DISCONNECTING = 4;
 
     // match up with bthf_audio_state_t enum of bt_hf.h
-    final static int AUDIO_STATE_DISCONNECTED = 0;
-    final static int AUDIO_STATE_CONNECTING = 1;
-    final static int AUDIO_STATE_CONNECTED = 2;
-    final static int AUDIO_STATE_DISCONNECTING = 3;
+    static final int AUDIO_STATE_DISCONNECTED = 0;
+    static final int AUDIO_STATE_CONNECTING = 1;
+    static final int AUDIO_STATE_CONNECTED = 2;
+    static final int AUDIO_STATE_DISCONNECTING = 3;
 
     // match up with bthf_vr_state_t enum of bt_hf.h
-    final static int VR_STATE_STOPPED = 0;
-    final static int VR_STATE_STARTED = 1;
+    static final int VR_STATE_STOPPED = 0;
+    static final int VR_STATE_STARTED = 1;
 
     // match up with bthf_volume_type_t enum of bt_hf.h
-    final static int VOLUME_TYPE_SPK = 0;
-    final static int VOLUME_TYPE_MIC = 1;
+    static final int VOLUME_TYPE_SPK = 0;
+    static final int VOLUME_TYPE_MIC = 1;
 
     // match up with bthf_network_state_t enum of bt_hf.h
-    final static int NETWORK_STATE_NOT_AVAILABLE = 0;
-    final static int NETWORK_STATE_AVAILABLE = 1;
+    static final int NETWORK_STATE_NOT_AVAILABLE = 0;
+    static final int NETWORK_STATE_AVAILABLE = 1;
 
     // match up with bthf_service_type_t enum of bt_hf.h
-    final static int SERVICE_TYPE_HOME = 0;
-    final static int SERVICE_TYPE_ROAMING = 1;
+    static final int SERVICE_TYPE_HOME = 0;
+    static final int SERVICE_TYPE_ROAMING = 1;
 
     // match up with bthf_at_response_t of bt_hf.h
-    final static int AT_RESPONSE_ERROR = 0;
-    final static int AT_RESPONSE_OK = 1;
+    static final int AT_RESPONSE_ERROR = 0;
+    static final int AT_RESPONSE_OK = 1;
 
     // match up with bthf_call_state_t of bt_hf.h
-    final static int CALL_STATE_ACTIVE = 0;
-    final static int CALL_STATE_HELD = 1;
-    final static int CALL_STATE_DIALING = 2;
-    final static int CALL_STATE_ALERTING = 3;
-    final static int CALL_STATE_INCOMING = 4;
-    final static int CALL_STATE_WAITING = 5;
-    final static int CALL_STATE_IDLE = 6;
+    static final int CALL_STATE_ACTIVE = 0;
+    static final int CALL_STATE_HELD = 1;
+    static final int CALL_STATE_DIALING = 2;
+    static final int CALL_STATE_ALERTING = 3;
+    static final int CALL_STATE_INCOMING = 4;
+    static final int CALL_STATE_WAITING = 5;
+    static final int CALL_STATE_IDLE = 6;
+    static final int CALL_STATE_DISCONNECTED = 7;
 
-    // Match up with bthf_hf_ind_type_t of bt_hf.h
-    final static int HF_INDICATOR_ENHANCED_DRIVER_SAFETY = 1;
-    public final static int HF_INDICATOR_BATTERY_LEVEL_STATUS = 2;
+    // match up with bthf_hf_ind_type_t of bt_hf.h
+    static final int HF_INDICATOR_ENHANCED_DRIVER_SAFETY = 1;
+    public static final int HF_INDICATOR_BATTERY_LEVEL_STATUS = 2;
+
+    // match up with bthf_wbs_config_t of bt_hf.h
+    static final int BTHF_WBS_NONE = 0;
+    static final int BTHF_WBS_NO = 1;
+    static final int BTHF_WBS_YES = 2;
 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetMessageObject.java b/src/com/android/bluetooth/hfp/HeadsetMessageObject.java
new file mode 100644
index 0000000..e9ab90e
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetMessageObject.java
@@ -0,0 +1,37 @@
+/*
+ * 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.hfp;
+
+import android.os.Message;
+
+/**
+ * An object passed through {@link Message#obj} on state machines
+ */
+public abstract class HeadsetMessageObject {
+    /**
+     * Build a representation of this object in a {@link StringBuilder}
+     *
+     * @param builder {@link StringBuilder} provided to build the string
+     */
+    public abstract void buildString(StringBuilder builder);
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        buildString(builder);
+        return builder.toString();
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
new file mode 100644
index 0000000..b236d51
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright 2017 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.hfp;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+
+/**
+ * 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 HeadsetNativeInterface {
+    private static final String TAG = "HeadsetNativeInterface";
+
+    private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+    static {
+        classInitNative();
+    }
+
+    private static HeadsetNativeInterface sInterface;
+    private static final Object INSTANCE_LOCK = new Object();
+
+    private HeadsetNativeInterface() {}
+
+    /**
+     * This class is a singleton because native library should only be loaded once
+     *
+     * @return default instance
+     */
+    public static HeadsetNativeInterface getInstance() {
+        synchronized (INSTANCE_LOCK) {
+            if (sInterface == null) {
+                sInterface = new HeadsetNativeInterface();
+            }
+        }
+        return sInterface;
+    }
+
+    private void sendMessageToService(HeadsetStackEvent event) {
+        HeadsetService service = HeadsetService.getHeadsetService();
+        if (service != null) {
+            service.messageFromNative(event);
+        } else {
+            // Service must call cleanup() when quiting and native stack shouldn't send any event
+            // after cleanup() -> cleanupNative() is called.
+            Log.wtfStack(TAG, "FATAL: Stack sent event while service is not available: " + event);
+        }
+    }
+
+    private BluetoothDevice getDevice(byte[] address) {
+        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
+    }
+
+    void onConnectionStateChanged(int state, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, state,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    // Callbacks for native code
+
+    private void onAudioStateChanged(int state, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, state,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onVrStateChanged(int state, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, state,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onAnswerCall(byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL, getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onHangupCall(byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_HANGUP_CALL, getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onVolumeChanged(int type, int volume, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VOLUME_CHANGED, type, volume,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onDialCall(String number, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_DIAL_CALL, number,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onSendDtmf(int dtmf, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_SEND_DTMF, dtmf,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onNoiceReductionEnable(boolean enable, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_NOICE_REDUCTION, enable ? 1 : 0,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onWBS(int codec, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_WBS, codec, getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onAtChld(int chld, byte[] address) {
+        HeadsetStackEvent event = new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AT_CHLD, chld,
+                getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onAtCnum(byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onAtCind(byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AT_CIND, getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onAtCops(byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AT_COPS, getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onAtClcc(byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AT_CLCC, getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onUnknownAt(String atString, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT, atString,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onKeyPressed(byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onATBind(String atString, byte[] address) {
+        HeadsetStackEvent event = new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString,
+                getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onATBiev(int indId, int indValue, byte[] address) {
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIEV, indId, indValue,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    private void onAtBia(boolean service, boolean roam, boolean signal, boolean battery,
+            byte[] address) {
+        HeadsetAgIndicatorEnableState agIndicatorEnableState =
+                new HeadsetAgIndicatorEnableState(service, roam, signal, battery);
+        HeadsetStackEvent event =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA, agIndicatorEnableState,
+                        getDevice(address));
+        sendMessageToService(event);
+    }
+
+    // Native wrappers to help unit testing
+
+    /**
+     * Initialize native stack
+     *
+     * @param maxHfClients maximum number of headset clients that can be connected simultaneously
+     * @param inbandRingingEnabled whether in-band ringing is enabled on this AG
+     */
+    @VisibleForTesting
+    public void init(int maxHfClients, boolean inbandRingingEnabled) {
+        initializeNative(maxHfClients, inbandRingingEnabled);
+    }
+
+    /**
+     * Closes the interface
+     */
+    @VisibleForTesting
+    public void cleanup() {
+        cleanupNative();
+    }
+
+    /**
+     * ok/error response
+     *
+     * @param device target device
+     * @param responseCode 0 - ERROR, 1 - OK
+     * @param errorCode error code in case of ERROR
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean atResponseCode(BluetoothDevice device, int responseCode, int errorCode) {
+        return atResponseCodeNative(responseCode, errorCode, Utils.getByteAddress(device));
+    }
+
+    /**
+     * Pre-formatted AT response, typically in response to unknown AT cmd
+     *
+     * @param device target device
+     * @param responseString formatted AT response string
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean atResponseString(BluetoothDevice device, String responseString) {
+        return atResponseStringNative(responseString, Utils.getByteAddress(device));
+    }
+
+    /**
+     * Connect to headset
+     *
+     * @param device target headset
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean connectHfp(BluetoothDevice device) {
+        return connectHfpNative(Utils.getByteAddress(device));
+    }
+
+    /**
+     * Disconnect from headset
+     *
+     * @param device target headset
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean disconnectHfp(BluetoothDevice device) {
+        return disconnectHfpNative(Utils.getByteAddress(device));
+    }
+
+    /**
+     * Connect HFP audio (SCO) to headset
+     *
+     * @param device target headset
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean connectAudio(BluetoothDevice device) {
+        return connectAudioNative(Utils.getByteAddress(device));
+    }
+
+    /**
+     * Disconnect HFP audio (SCO) from to headset
+     *
+     * @param device target headset
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean disconnectAudio(BluetoothDevice device) {
+        return disconnectAudioNative(Utils.getByteAddress(device));
+    }
+
+    /**
+     * Start voice recognition
+     *
+     * @param device target headset
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean startVoiceRecognition(BluetoothDevice device) {
+        return startVoiceRecognitionNative(Utils.getByteAddress(device));
+    }
+
+
+    /**
+     * Stop voice recognition
+     *
+     * @param device target headset
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean stopVoiceRecognition(BluetoothDevice device) {
+        return stopVoiceRecognitionNative(Utils.getByteAddress(device));
+    }
+
+    /**
+     * Set HFP audio (SCO) volume
+     *
+     * @param device target headset
+     * @param volumeType type of volume
+     * @param volume value value
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean setVolume(BluetoothDevice device, int volumeType, int volume) {
+        return setVolumeNative(volumeType, volume, Utils.getByteAddress(device));
+    }
+
+    /**
+     * Response for CIND command
+     *
+     * @param device target device
+     * @param service service availability, 0 - no service, 1 - presence of service
+     * @param numActive number of active calls
+     * @param numHeld number of held calls
+     * @param callState overall call state [0-6]
+     * @param signal signal quality [0-5]
+     * @param roam roaming indicator, 0 - not roaming, 1 - roaming
+     * @param batteryCharge battery charge level [0-5]
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean cindResponse(BluetoothDevice device, int service, int numActive, int numHeld,
+            int callState, int signal, int roam, int batteryCharge) {
+        return cindResponseNative(service, numActive, numHeld, callState, signal, roam,
+                batteryCharge, Utils.getByteAddress(device));
+    }
+
+    /**
+     * Combined device status change notification
+     *
+     * @param device target device
+     * @param deviceState device status object
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean notifyDeviceStatus(BluetoothDevice device, HeadsetDeviceState deviceState) {
+        return notifyDeviceStatusNative(deviceState.mService, deviceState.mRoam,
+                deviceState.mSignal, deviceState.mBatteryCharge, Utils.getByteAddress(device));
+    }
+
+    /**
+     * Response for CLCC command. Can be iteratively called for each call index. Call index of 0
+     * will be treated as NULL termination (Completes response)
+     *
+     * @param device target device
+     * @param index index of the call given by the sequence of setting up or receiving the calls
+     * as seen by the served subscriber. Calls hold their number until they are released. New
+     * calls take the lowest available number.
+     * @param dir direction of the call, 0 (outgoing), 1 (incoming)
+     * @param status 0 = Active, 1 = Held, 2 = Dialing (outgoing calls only), 3 = Alerting
+     * (outgoing calls only), 4 = Incoming (incoming calls only), 5 = Waiting (incoming calls
+     * only), 6 = Call held by Response and Hold
+     * @param mode 0 (Voice), 1 (Data), 2 (FAX)
+     * @param mpty 0 - this call is NOT a member of a multi-party (conference) call, 1 - this
+     * call IS a member of a multi-party (conference) call
+     * @param number optional
+     * @param type optional
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean clccResponse(BluetoothDevice device, int index, int dir, int status, int mode,
+            boolean mpty, String number, int type) {
+        return clccResponseNative(index, dir, status, mode, mpty, number, type,
+                Utils.getByteAddress(device));
+    }
+
+    /**
+     * Response for COPS command
+     *
+     * @param device target device
+     * @param operatorName operator name
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean copsResponse(BluetoothDevice device, String operatorName) {
+        return copsResponseNative(operatorName, Utils.getByteAddress(device));
+    }
+
+    /**
+     *  Notify of a call state change
+     *  Each update notifies
+     *    1. Number of active/held/ringing calls
+     *    2. call_state: This denotes the state change that triggered this msg
+     *                   This will take one of the values from BtHfCallState
+     *    3. number & type: valid only for incoming & waiting call
+     *
+     * @param device target device for this update
+     * @param callState callstate structure
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean phoneStateChange(BluetoothDevice device, HeadsetCallState callState) {
+        return phoneStateChangeNative(callState.mNumActive, callState.mNumHeld,
+                callState.mCallState, callState.mNumber, callState.mType,
+                Utils.getByteAddress(device));
+    }
+
+    /**
+     * Set whether we will initiate SCO or not
+     *
+     * @param value True to enable, False to disable
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean setScoAllowed(boolean value) {
+        return setScoAllowedNative(value);
+    }
+
+    /**
+     * Enable or disable in-band ringing for the current service level connection through sending
+     * +BSIR AT command
+     *
+     * @param value True to enable, False to disable
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean sendBsir(BluetoothDevice device, boolean value) {
+        return sendBsirNative(value, Utils.getByteAddress(device));
+    }
+
+    /**
+     * Set the current active headset device for SCO audio
+     * @param device current active SCO device
+     * @return true on success
+     */
+    @VisibleForTesting
+    public boolean setActiveDevice(BluetoothDevice device) {
+        return setActiveDeviceNative(Utils.getByteAddress(device));
+    }
+
+    /* Native methods */
+    private static native void classInitNative();
+
+    private native boolean atResponseCodeNative(int responseCode, int errorCode, byte[] address);
+
+    private native boolean atResponseStringNative(String responseString, byte[] address);
+
+    private native void initializeNative(int maxHfClients, boolean inbandRingingEnabled);
+
+    private native void cleanupNative();
+
+    private native boolean connectHfpNative(byte[] address);
+
+    private native boolean disconnectHfpNative(byte[] address);
+
+    private native boolean connectAudioNative(byte[] address);
+
+    private native boolean disconnectAudioNative(byte[] address);
+
+    private native boolean startVoiceRecognitionNative(byte[] address);
+
+    private native boolean stopVoiceRecognitionNative(byte[] address);
+
+    private native boolean setVolumeNative(int volumeType, int volume, byte[] address);
+
+    private native boolean cindResponseNative(int service, int numActive, int numHeld,
+            int callState, int signal, int roam, int batteryCharge, byte[] address);
+
+    private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal,
+            int batteryCharge, byte[] address);
+
+    private native boolean clccResponseNative(int index, int dir, int status, int mode,
+            boolean mpty, String number, int type, byte[] address);
+
+    private native boolean copsResponseNative(String operatorName, byte[] address);
+
+    private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState,
+            String number, int type, byte[] address);
+
+    private native boolean setScoAllowedNative(boolean value);
+
+    private native boolean sendBsirNative(boolean value, byte[] address);
+
+    private native boolean setActiveDeviceNative(byte[] address);
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetObjectsFactory.java b/src/com/android/bluetooth/hfp/HeadsetObjectsFactory.java
new file mode 100644
index 0000000..3f5867f
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetObjectsFactory.java
@@ -0,0 +1,107 @@
+/*
+ * 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.hfp;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+
+/**
+ * Factory class for object initialization to help with unit testing
+ */
+public class HeadsetObjectsFactory {
+    private static final String TAG = HeadsetObjectsFactory.class.getSimpleName();
+    private static HeadsetObjectsFactory sInstance;
+    private static final Object INSTANCE_LOCK = new Object();
+
+    private HeadsetObjectsFactory() {}
+
+    /**
+     * Get the singleton instance of object factory
+     *
+     * @return the singleton instance, guaranteed not null
+     */
+    public static HeadsetObjectsFactory getInstance() {
+        synchronized (INSTANCE_LOCK) {
+            if (sInstance == null) {
+                sInstance = new HeadsetObjectsFactory();
+            }
+        }
+        return sInstance;
+    }
+
+    /**
+     * Allow unit tests to substitute HeadsetObjectsFactory with a test instance
+     *
+     * @param objectsFactory a test instance of the HeadsetObjectsFactory
+     */
+    private static void setInstanceForTesting(HeadsetObjectsFactory objectsFactory) {
+        Utils.enforceInstrumentationTestMode();
+        synchronized (INSTANCE_LOCK) {
+            Log.d(TAG, "setInstanceForTesting(), set to " + objectsFactory);
+            sInstance = objectsFactory;
+        }
+    }
+
+    /**
+     * Make a {@link HeadsetStateMachine}
+     *
+     * @param device the remote device associated with this state machine
+     * @param looper the thread that the state machine is supposed to run on
+     * @param headsetService the headset service
+     * @param adapterService the adapter service
+     * @param nativeInterface native interface
+     * @param systemInterface system interface
+     * @return a state machine that is initialized and started, ready to go
+     */
+    public HeadsetStateMachine makeStateMachine(BluetoothDevice device, Looper looper,
+            HeadsetService headsetService, AdapterService adapterService,
+            HeadsetNativeInterface nativeInterface, HeadsetSystemInterface systemInterface) {
+        return HeadsetStateMachine.make(device, looper, headsetService, adapterService,
+                nativeInterface, systemInterface);
+    }
+
+    /**
+     * Destroy a state machine
+     *
+     * @param stateMachine to be destroyed. Cannot be used after this call.
+     */
+    public void destroyStateMachine(HeadsetStateMachine stateMachine) {
+        HeadsetStateMachine.destroy(stateMachine);
+    }
+
+    /**
+     * Get a system interface
+     *
+     * @param service headset service
+     * @return a system interface
+     */
+    public HeadsetSystemInterface makeSystemInterface(HeadsetService service) {
+        return new HeadsetSystemInterface(service);
+    }
+
+    /**
+     * Get a singleton native interface
+     *
+     * @return the singleton native interface
+     */
+    public HeadsetNativeInterface getNativeInterface() {
+        return HeadsetNativeInterface.getInstance();
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index 8440d4b..bcf123c 100644
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -21,157 +21,179 @@
 import android.content.Context;
 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;
-import android.telephony.TelephonyManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
 
+import java.util.HashMap;
+import java.util.Objects;
 
-// Note:
-// All methods in this class are not thread safe, donot call them from
-// multiple threads. Call them from the HeadsetPhoneStateMachine message
-// handler only.
-class HeadsetPhoneState {
+
+/**
+ * Class that manages Telephony states
+ *
+ * Note:
+ * The methods in this class are not thread safe, don't call them from
+ * multiple threads. Call them from the HeadsetPhoneStateMachine message
+ * handler only.
+ */
+public class HeadsetPhoneState {
     private static final String TAG = "HeadsetPhoneState";
 
-    private HeadsetStateMachine mStateMachine;
-    private TelephonyManager mTelephonyManager;
+    private final HeadsetService mHeadsetService;
+    private final TelephonyManager mTelephonyManager;
+    private final SubscriptionManager mSubscriptionManager;
+
     private ServiceState mServiceState;
 
-    // HFP 1.6 CIND service
-    private int mService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
-
+    // HFP 1.6 CIND service value
+    private int mCindService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
     // Check this before sending out service state to the device -- if the SIM isn't fully
     // loaded, don't expose that the network is available.
-    private boolean mIsSimStateLoaded = false;
-
+    private boolean mIsSimStateLoaded;
     // Number of active (foreground) calls
-    private int mNumActive = 0;
-
+    private int mNumActive;
     // Current Call Setup State
     private int mCallState = HeadsetHalConstants.CALL_STATE_IDLE;
-
     // Number of held (background) calls
-    private int mNumHeld = 0;
+    private int mNumHeld;
+    // HFP 1.6 CIND signal value
+    private int mCindSignal;
+    // HFP 1.6 CIND roam value
+    private int mCindRoam = HeadsetHalConstants.SERVICE_TYPE_HOME;
+    // HFP 1.6 CIND battchg value
+    private int mCindBatteryCharge;
 
-    // HFP 1.6 CIND signal
-    private int mSignal = 0;
+    private final HashMap<BluetoothDevice, Integer> mDeviceEventMap = new HashMap<>();
+    private PhoneStateListener mPhoneStateListener;
+    private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
 
-    // HFP 1.6 CIND roam
-    private int mRoam = HeadsetHalConstants.SERVICE_TYPE_HOME;
-
-    // HFP 1.6 CIND battchg
-    private int mBatteryCharge = 0;
-
-    private int mSpeakerVolume = 0;
-
-    private int mMicVolume = 0;
-
-    private boolean mListening = false;
-
-    // when HFP Service Level Connection is established
-    private boolean mSlcReady = false;
-
-    private Context mContext = null;
-
-    private PhoneStateListener mPhoneStateListener = null;
-
-    private SubscriptionManager mSubMgr;
-
-    private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
-            new OnSubscriptionsChangedListener() {
-        @Override
-        public void onSubscriptionsChanged() {
-            listenForPhoneState(false);
-            listenForPhoneState(true);
-        }
-    };
-
-
-    HeadsetPhoneState(Context context, HeadsetStateMachine stateMachine) {
-        mStateMachine = stateMachine;
-        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        if (mTelephonyManager == null) {
-            Log.e(TAG, "getSystemService(Context.TELEPHONY_SERVICE) failed, "
-                  + "cannot register for SubscriptionInfo changes");
-        }
-        mContext = context;
-
-        // Register for SubscriptionInfo list changes which is guaranteed
-        // to invoke onSubscriptionInfoChanged and which in turns calls
-        // loadInBackgroud.
-        mSubMgr = SubscriptionManager.from(mContext);
-        mSubMgr.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+    HeadsetPhoneState(HeadsetService headsetService) {
+        Objects.requireNonNull(headsetService, "headsetService is null");
+        mHeadsetService = headsetService;
+        mTelephonyManager =
+                (TelephonyManager) mHeadsetService.getSystemService(Context.TELEPHONY_SERVICE);
+        Objects.requireNonNull(mTelephonyManager, "TELEPHONY_SERVICE is null");
+        // Register for SubscriptionInfo list changes which is guaranteed to invoke
+        // onSubscriptionInfoChanged and which in turns calls loadInBackgroud.
+        mSubscriptionManager = SubscriptionManager.from(mHeadsetService);
+        Objects.requireNonNull(mSubscriptionManager, "TELEPHONY_SUBSCRIPTION_SERVICE is null");
+        // Initialize subscription on the handler thread
+        mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener(
+                headsetService.getStateMachinesThreadLooper());
+        mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
     }
 
+    /**
+     * Cleanup this instance. Instance can no longer be used after calling this method.
+     */
     public void cleanup() {
-        listenForPhoneState(false);
-        mSubMgr.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
-
-        mTelephonyManager = null;
-        mStateMachine = null;
-    }
-
-    void listenForPhoneState(boolean start) {
-
-        mSlcReady = start;
-
-        if (start) {
-            startListenForPhoneState();
-        } else {
+        synchronized (mDeviceEventMap) {
+            mDeviceEventMap.clear();
             stopListenForPhoneState();
         }
+        mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+    }
 
+    @Override
+    public String toString() {
+        return "HeadsetPhoneState [mTelephonyServiceAvailability=" + mCindService + ", mNumActive="
+                + mNumActive + ", mCallState=" + mCallState + ", mNumHeld=" + mNumHeld
+                + ", mSignal=" + mCindSignal + ", mRoam=" + mCindRoam + ", mBatteryCharge="
+                + mCindBatteryCharge + ", TelephonyEvents=" + getTelephonyEventsToListen() + "]";
+    }
+
+    private int getTelephonyEventsToListen() {
+        synchronized (mDeviceEventMap) {
+            return mDeviceEventMap.values()
+                    .stream()
+                    .reduce(PhoneStateListener.LISTEN_NONE, (a, b) -> a | b);
+        }
+    }
+
+    /**
+     * Start or stop listening for phone state change
+     *
+     * @param device remote device that subscribes to this phone state update
+     * @param events events in {@link PhoneStateListener} to listen to
+     */
+    @VisibleForTesting
+    public void listenForPhoneState(BluetoothDevice device, int events) {
+        synchronized (mDeviceEventMap) {
+            int prevEvents = getTelephonyEventsToListen();
+            if (events == PhoneStateListener.LISTEN_NONE) {
+                mDeviceEventMap.remove(device);
+            } else {
+                mDeviceEventMap.put(device, events);
+            }
+            int updatedEvents = getTelephonyEventsToListen();
+            if (prevEvents != updatedEvents) {
+                stopListenForPhoneState();
+                startListenForPhoneState();
+            }
+        }
     }
 
     private void startListenForPhoneState() {
-        if (!mListening && mSlcReady && mTelephonyManager != null) {
-
-            int subId = SubscriptionManager.getDefaultSubscriptionId();
-
-            if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                mPhoneStateListener = getPhoneStateListener(subId);
-                if (mTelephonyManager == null) {
-                    Log.e(TAG, "mTelephonyManager is null, "
-                         + "cannot start listening for phone state changes");
-                } else {
-                    mTelephonyManager.listen(mPhoneStateListener,
-                                             PhoneStateListener.LISTEN_SERVICE_STATE |
-                                             PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
-                    mListening = true;
-                }
-            }
+        if (mPhoneStateListener != null) {
+            Log.w(TAG, "startListenForPhoneState, already listening");
+            return;
+        }
+        int events = getTelephonyEventsToListen();
+        if (events == PhoneStateListener.LISTEN_NONE) {
+            Log.w(TAG, "startListenForPhoneState, no event to listen");
+            return;
+        }
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            // Will retry listening for phone state in onSubscriptionsChanged() callback
+            Log.w(TAG, "startListenForPhoneState, invalid subscription ID " + subId);
+            return;
+        }
+        Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
+        mPhoneStateListener = new HeadsetPhoneStateListener(subId,
+                mHeadsetService.getStateMachinesThreadLooper());
+        mTelephonyManager.listen(mPhoneStateListener, events);
+        if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
+            mTelephonyManager.setRadioIndicationUpdateMode(
+                    TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                    TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
         }
     }
 
     private void stopListenForPhoneState() {
-        if (mListening && mTelephonyManager != null) {
-
-            if (mTelephonyManager == null) {
-                Log.e(TAG, "mTelephonyManager is null, "
-                     + "cannot send request to stop listening for phone state changes");
-            } else {
-                mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-                mListening = false;
-            }
+        if (mPhoneStateListener == null) {
+            Log.i(TAG, "stopListenForPhoneState(), no listener indicates nothing is listening");
+            return;
         }
+        Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events="
+                + getTelephonyEventsToListen());
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+        mTelephonyManager.setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+        mPhoneStateListener = null;
     }
 
-    int getService() {
-        return mService;
+    int getCindService() {
+        return mCindService;
     }
 
     int getNumActiveCall() {
         return mNumActive;
     }
 
-    void setNumActiveCall(int numActive) {
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public void setNumActiveCall(int numActive) {
         mNumActive = numActive;
     }
 
@@ -179,7 +201,8 @@
         return mCallState;
     }
 
-    void setCallState(int callState) {
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public void setCallState(int callState) {
         mCallState = callState;
     }
 
@@ -187,284 +210,131 @@
         return mNumHeld;
     }
 
-    void setNumHeldCall(int numHeldCall) {
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public void setNumHeldCall(int numHeldCall) {
         mNumHeld = numHeldCall;
     }
 
-    int getSignal() {
-        return mSignal;
+    int getCindSignal() {
+        return mCindSignal;
     }
 
-    int getRoam() {
-        return mRoam;
+    int getCindRoam() {
+        return mCindRoam;
     }
 
-    void setRoam(int roam) {
-        if (mRoam != roam) {
-            mRoam = roam;
+    /**
+     * Set battery level value used for +CIND result
+     *
+     * @param batteryLevel battery level value
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public void setCindBatteryCharge(int batteryLevel) {
+        if (mCindBatteryCharge != batteryLevel) {
+            mCindBatteryCharge = batteryLevel;
             sendDeviceStateChanged();
         }
     }
 
-    void setBatteryCharge(int batteryLevel) {
-        if (mBatteryCharge != batteryLevel) {
-            mBatteryCharge = batteryLevel;
-            sendDeviceStateChanged();
-        }
-    }
-
-    int getBatteryCharge() {
-        return mBatteryCharge;
-    }
-
-    void setSpeakerVolume(int volume) {
-        mSpeakerVolume = volume;
-    }
-
-    int getSpeakerVolume() {
-        return mSpeakerVolume;
-    }
-
-    void setMicVolume(int volume) {
-        mMicVolume = volume;
-    }
-
-    int getMicVolume() {
-        return mMicVolume;
+    int getCindBatteryCharge() {
+        return mCindBatteryCharge;
     }
 
     boolean isInCall() {
         return (mNumActive >= 1);
     }
 
-    void sendDeviceStateChanged()
-    {
+    private void sendDeviceStateChanged() {
         int service =
-                mIsSimStateLoaded ? mService : HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
+                mIsSimStateLoaded ? mCindService : HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
         // When out of service, send signal strength as 0. Some devices don't
         // use the service indicator, but only the signal indicator
-        int signal = service == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mSignal : 0;
+        int signal = service == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mCindSignal : 0;
 
-        Log.d(TAG, "sendDeviceStateChanged. mService="+ mService +
-                   " mIsSimStateLoaded=" + mIsSimStateLoaded +
-                   " mSignal=" + signal +" mRoam="+ mRoam +
-                   " mBatteryCharge=" + mBatteryCharge);
-        HeadsetStateMachine sm = mStateMachine;
-        if (sm != null) {
-            sm.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
-                new HeadsetDeviceState(service, mRoam, signal, mBatteryCharge));
+        Log.d(TAG, "sendDeviceStateChanged. mService=" + mCindService + " mIsSimStateLoaded="
+                + mIsSimStateLoaded + " mSignal=" + signal + " mRoam=" + mCindRoam
+                + " mBatteryCharge=" + mCindBatteryCharge);
+        mHeadsetService.onDeviceStateChanged(
+                new HeadsetDeviceState(service, mCindRoam, signal, mCindBatteryCharge));
+    }
+
+    private class HeadsetPhoneStateOnSubscriptionChangedListener
+            extends OnSubscriptionsChangedListener {
+        HeadsetPhoneStateOnSubscriptionChangedListener(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void onSubscriptionsChanged() {
+            synchronized (mDeviceEventMap) {
+                stopListenForPhoneState();
+                startListenForPhoneState();
+            }
         }
     }
 
-    private PhoneStateListener getPhoneStateListener(int subId) {
-        PhoneStateListener mPhoneStateListener = new PhoneStateListener(subId) {
-            @Override
-            public void onServiceStateChanged(ServiceState serviceState) {
-                mServiceState = serviceState;
-                int newService = (serviceState.getState() == ServiceState.STATE_IN_SERVICE) ?
-                    HeadsetHalConstants.NETWORK_STATE_AVAILABLE :
-                    HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
-                int newRoam = serviceState.getRoaming() ? HeadsetHalConstants.SERVICE_TYPE_ROAMING
-                                                  : HeadsetHalConstants.SERVICE_TYPE_HOME;
+    private class HeadsetPhoneStateListener extends PhoneStateListener {
+        HeadsetPhoneStateListener(Integer subId, Looper looper) {
+            super(subId, looper);
+        }
 
-                if (newService == mService && newRoam == mRoam) {
-                    // Debounce the state change
-                    return;
-                }
-                mService = newService;
-                mRoam = newRoam;
+        @Override
+        public synchronized void onServiceStateChanged(ServiceState serviceState) {
+            mServiceState = serviceState;
+            int cindService = (serviceState.getState() == ServiceState.STATE_IN_SERVICE)
+                    ? HeadsetHalConstants.NETWORK_STATE_AVAILABLE
+                    : HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
+            int newRoam = serviceState.getRoaming() ? HeadsetHalConstants.SERVICE_TYPE_ROAMING
+                    : HeadsetHalConstants.SERVICE_TYPE_HOME;
 
-                // If this is due to a SIM insertion, we want to defer sending device state changed
-                // until all the SIM config is loaded.
-                if (newService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
-                    mIsSimStateLoaded = false;
-                    sendDeviceStateChanged();
-                    return;
-                }
-                IntentFilter simStateChangedFilter =
-                        new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-                mContext.registerReceiver(new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
-                            // This is a sticky broadcast, so if it's already been loaded,
-                            // this'll execute immediately.
-                            if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(
-                                    intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) {
-                                mIsSimStateLoaded = true;
-                                sendDeviceStateChanged();
-                                mContext.unregisterReceiver(this);
-                            }
+            if (cindService == mCindService && newRoam == mCindRoam) {
+                // De-bounce the state change
+                return;
+            }
+            mCindService = cindService;
+            mCindRoam = newRoam;
+
+            // If this is due to a SIM insertion, we want to defer sending device state changed
+            // until all the SIM config is loaded.
+            if (cindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
+                mIsSimStateLoaded = false;
+                sendDeviceStateChanged();
+                return;
+            }
+            IntentFilter simStateChangedFilter =
+                    new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+            mHeadsetService.registerReceiver(new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
+                        // This is a sticky broadcast, so if it's already been loaded,
+                        // this'll execute immediately.
+                        if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(
+                                intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) {
+                            mIsSimStateLoaded = true;
+                            sendDeviceStateChanged();
+                            mHeadsetService.unregisterReceiver(this);
                         }
                     }
-                }, simStateChangedFilter);
-            }
-
-            @Override
-            public void onSignalStrengthsChanged(SignalStrength signalStrength) {
-
-                int prevSignal = mSignal;
-                if (mService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
-                    mSignal = 0;
-                } else if (signalStrength.isGsm()) {
-                    mSignal = signalStrength.getLteLevel();
-                    if (mSignal == SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                        mSignal = gsmAsuToSignal(signalStrength);
-                    } else {
-                        // SignalStrength#getLteLevel returns the scale from 0-4
-                        // Bluetooth signal scales at 0-5
-                        // Let's match up the larger side
-                        mSignal++;
-                    }
-                } else {
-                    mSignal = cdmaDbmEcioToSignal(signalStrength);
                 }
+            }, simStateChangedFilter);
 
-                // network signal strength is scaled to BT 1-5 levels.
-                // This results in a lot of duplicate messages, hence this check
-                if (prevSignal != mSignal) {
-                    sendDeviceStateChanged();
-                }
+        }
+
+        @Override
+        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+            int prevSignal = mCindSignal;
+            if (mCindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
+                mCindSignal = 0;
+            } else {
+                mCindSignal = signalStrength.getLevel() + 1;
             }
-
-            /* convert [0,31] ASU signal strength to the [0,5] expected by
-             * bluetooth devices. Scale is similar to status bar policy
-             */
-            private int gsmAsuToSignal(SignalStrength signalStrength) {
-                int asu = signalStrength.getGsmSignalStrength();
-                if      (asu == 99) return 0;
-                else if (asu >= 16) return 5;
-                else if (asu >= 8)  return 4;
-                else if (asu >= 4)  return 3;
-                else if (asu >= 2)  return 2;
-                else if (asu >= 1)  return 1;
-                else                return 0;
+            // +CIND "signal" indicator is always between 0 to 5
+            mCindSignal = Integer.max(Integer.min(mCindSignal, 5), 0);
+            // This results in a lot of duplicate messages, hence this check
+            if (prevSignal != mCindSignal) {
+                sendDeviceStateChanged();
             }
-
-            /**
-             * Convert the cdma / evdo db levels to appropriate icon level.
-             * The scale is similar to the one used in status bar policy.
-             *
-             * @param signalStrength
-             * @return the icon level
-             */
-            private int cdmaDbmEcioToSignal(SignalStrength signalStrength) {
-                int levelDbm = 0;
-                int levelEcio = 0;
-                int cdmaIconLevel = 0;
-                int evdoIconLevel = 0;
-                int cdmaDbm = signalStrength.getCdmaDbm();
-                int cdmaEcio = signalStrength.getCdmaEcio();
-
-                if (cdmaDbm >= -75) levelDbm = 4;
-                else if (cdmaDbm >= -85) levelDbm = 3;
-                else if (cdmaDbm >= -95) levelDbm = 2;
-                else if (cdmaDbm >= -100) levelDbm = 1;
-                else levelDbm = 0;
-
-                // Ec/Io are in dB*10
-                if (cdmaEcio >= -90) levelEcio = 4;
-                else if (cdmaEcio >= -110) levelEcio = 3;
-                else if (cdmaEcio >= -130) levelEcio = 2;
-                else if (cdmaEcio >= -150) levelEcio = 1;
-                else levelEcio = 0;
-
-                cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;
-
-                // STOPSHIP: Change back to getRilVoiceRadioTechnology
-                if (mServiceState != null &&
-                      (mServiceState.getRadioTechnology() ==
-                          ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 ||
-                       mServiceState.getRadioTechnology() ==
-                           ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)) {
-                      int evdoEcio = signalStrength.getEvdoEcio();
-                      int evdoSnr = signalStrength.getEvdoSnr();
-                      int levelEvdoEcio = 0;
-                      int levelEvdoSnr = 0;
-
-                      // Ec/Io are in dB*10
-                      if (evdoEcio >= -650) levelEvdoEcio = 4;
-                      else if (evdoEcio >= -750) levelEvdoEcio = 3;
-                      else if (evdoEcio >= -900) levelEvdoEcio = 2;
-                      else if (evdoEcio >= -1050) levelEvdoEcio = 1;
-                      else levelEvdoEcio = 0;
-
-                      if (evdoSnr > 7) levelEvdoSnr = 4;
-                      else if (evdoSnr > 5) levelEvdoSnr = 3;
-                      else if (evdoSnr > 3) levelEvdoSnr = 2;
-                      else if (evdoSnr > 1) levelEvdoSnr = 1;
-                      else levelEvdoSnr = 0;
-
-                      evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr;
-                }
-                // TODO(): There is a bug open regarding what should be sent.
-                return (cdmaIconLevel > evdoIconLevel) ?  cdmaIconLevel : evdoIconLevel;
-            }
-        };
-        return mPhoneStateListener;
-    }
-
-}
-
-class HeadsetDeviceState {
-    int mService;
-    int mRoam;
-    int mSignal;
-    int mBatteryCharge;
-
-    HeadsetDeviceState(int service, int roam, int signal, int batteryCharge) {
-        mService = service;
-        mRoam = roam;
-        mSignal = signal;
-        mBatteryCharge = batteryCharge;
-    }
-}
-
-class HeadsetCallState {
-    int mNumActive;
-    int mNumHeld;
-    int mCallState;
-    String mNumber;
-    int mType;
-
-    public HeadsetCallState(int numActive, int numHeld, int callState, String number, int type) {
-        mNumActive = numActive;
-        mNumHeld = numHeld;
-        mCallState = callState;
-        mNumber = number;
-        mType = type;
-    }
-}
-
-class HeadsetClccResponse {
-    int mIndex;
-    int mDirection;
-    int mStatus;
-    int mMode;
-    boolean mMpty;
-    String mNumber;
-    int mType;
-
-    public HeadsetClccResponse(int index, int direction, int status, int mode, boolean mpty,
-                               String number, int type) {
-        mIndex = index;
-        mDirection = direction;
-        mStatus = status;
-        mMode = mode;
-        mMpty = mpty;
-        mNumber = number;
-        mType = type;
-    }
-}
-
-class HeadsetVendorSpecificResultCode {
-    BluetoothDevice mDevice;
-    String mCommand;
-    String mArg;
-
-    public HeadsetVendorSpecificResultCode(BluetoothDevice device, String command, String arg) {
-        mDevice = device;
-        mCommand = command;
-        mArg = arg;
+        }
     }
 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index d1fc4a5..6d16a6a 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -16,97 +16,380 @@
 
 package com.android.bluetooth.hfp;
 
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothHeadset;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.os.Message;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.HandlerThread;
+import android.os.IDeviceIdleController;
+import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+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 com.android.bluetooth.btservice.ProfileService;
+
+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;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Objects;
 
 /**
- * Provides Bluetooth Headset and Handsfree profile, as a service in
- * the Bluetooth application.
- * @hide
+ * 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 2: Virtual call through {@link #startScoUsingVirtualVoiceCall()}
+ * Mode 3: Voice recognition through {@link #startVoiceRecognition(BluetoothDevice)}
+ *
+ * When one mode is active, other mode cannot be started. API user has to terminate existing modes
+ * using the correct API or just {@link #disconnectAudio()} if user is a system service, before
+ * starting a new mode.
+ *
+ * {@link #connectAudio()} will start SCO audio at one of the above modes, but won't change mode
+ * {@link #disconnectAudio()} can happen in any mode to disconnect SCO
+ *
+ * When audio is disconnected, only Mode 1 Telecom call will be persisted, both Mode 2 virtual call
+ * and Mode 3 voice call will be terminated upon SCO termination and client has to restart the mode.
+ *
+ * NOTE: SCO termination can either be initiated on the AG side or the HF side
+ * TODO(b/79660380): As a workaround, voice recognition will be terminated if virtual call or
+ * Telecom call is initiated while voice recognition is ongoing, in case calling app did not call
+ * {@link #stopVoiceRecognition(BluetoothDevice)}
+ *
+ * AG - Audio Gateway, device running this {@link HeadsetService}, e.g. Android Phone
+ * HF - Handsfree device, device running headset client, e.g. Wireless headphones or car kits
  */
 public class HeadsetService extends ProfileService {
-    private static final boolean DBG = false;
     private static final String TAG = "HeadsetService";
-    private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE;
+    private static final boolean DBG = false;
+    private static final String DISABLE_INBAND_RINGING_PROPERTY =
+            "persist.bluetooth.disableinbandringing";
+    private static final ParcelUuid[] HEADSET_UUIDS = {BluetoothUuid.HSP, BluetoothUuid.Handsfree};
+    private static final int[] CONNECTING_CONNECTED_STATES =
+            {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED};
+    private static final int DIALING_OUT_TIMEOUT_MS = 10000;
 
-    private HeadsetStateMachine mStateMachine;
+    private int mMaxHeadsetConnections = 1;
+    private BluetoothDevice mActiveDevice;
+    private AdapterService mAdapterService;
+    private HandlerThread mStateMachinesThread;
+    // This is also used as a lock for shared data in HeadsetService
+    private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>();
+    private HeadsetNativeInterface mNativeInterface;
+    private HeadsetSystemInterface mSystemInterface;
+    private boolean mAudioRouteAllowed = true;
+    // Indicates whether SCO audio needs to be forced to open regardless ANY OTHER restrictions
+    private boolean mForceScoAudio;
+    private boolean mInbandRingingRuntimeDisable;
+    private boolean mVirtualCallStarted;
+    // Non null value indicates a pending dialing out event is going on
+    private DialingOutTimeoutEvent mDialingOutTimeoutEvent;
+    private boolean mVoiceRecognitionStarted;
+    // Non null value indicates a pending voice recognition request from headset is going on
+    private VoiceRecognitionTimeoutEvent mVoiceRecognitionTimeoutEvent;
+    // Timeout when voice recognition is started by remote device
+    @VisibleForTesting static int sStartVrTimeoutMs = 5000;
+    private boolean mStarted;
+    private boolean mCreated;
     private static HeadsetService sHeadsetService;
 
-    protected String getName() {
-        return TAG;
-    }
-
+    @Override
     public IProfileServiceBinder initBinder() {
         return new BluetoothHeadsetBinder(this);
     }
 
+    @Override
+    protected void create() {
+        Log.i(TAG, "create()");
+        if (mCreated) {
+            throw new IllegalStateException("create() called twice");
+        }
+        mCreated = true;
+    }
+
+    @Override
     protected boolean start() {
-        mStateMachine = HeadsetStateMachine.make(this);
-        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        Log.i(TAG, "start()");
+        if (mStarted) {
+            throw new IllegalStateException("start() called twice");
+        }
+        // Step 1: Get adapter service, should never be null
+        mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+                "AdapterService cannot be null when HeadsetService starts");
+        // Step 2: Start handler thread for state machines
+        mStateMachinesThread = new HandlerThread("HeadsetService.StateMachines");
+        mStateMachinesThread.start();
+        // Step 3: Initialize system interface
+        mSystemInterface = HeadsetObjectsFactory.getInstance().makeSystemInterface(this);
+        mSystemInterface.init();
+        // Step 4: Initialize native interface
+        mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices();
+        mNativeInterface = HeadsetObjectsFactory.getInstance().getNativeInterface();
+        // Add 1 to allow a pending device to be connecting or disconnecting
+        mNativeInterface.init(mMaxHeadsetConnections + 1, isInbandRingingEnabled());
+        // Step 5: Check if state machine table is empty, crash if not
+        if (mStateMachines.size() > 0) {
+            throw new IllegalStateException(
+                    "start(): mStateMachines is not empty, " + mStateMachines.size()
+                            + " is already created. Was stop() called properly?");
+        }
+        // Step 6: Setup broadcast receivers
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
         filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
-        try {
-            registerReceiver(mHeadsetReceiver, filter);
-        } catch (Exception e) {
-            Log.w(TAG, "Unable to register headset receiver", e);
-        }
+        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        registerReceiver(mHeadsetReceiver, filter);
+        // Step 7: Mark service as started
         setHeadsetService(this);
+        mStarted = true;
         return true;
     }
 
+    @Override
     protected boolean stop() {
-        try {
-            unregisterReceiver(mHeadsetReceiver);
-        } catch (Exception e) {
-            Log.w(TAG, "Unable to unregister headset receiver", e);
+        Log.i(TAG, "stop()");
+        if (!mStarted) {
+            Log.w(TAG, "stop() called before start()");
+            // Still return true because it is considered "stopped" and doesn't have any functional
+            // impact on the user
+            return true;
         }
-        if (mStateMachine != null) {
-            mStateMachine.doQuit();
+        // Step 7: Mark service as stopped
+        mStarted = false;
+        setHeadsetService(null);
+        // Step 6: Tear down broadcast receivers
+        unregisterReceiver(mHeadsetReceiver);
+        synchronized (mStateMachines) {
+            // Reset active device to null
+            mActiveDevice = null;
+            mInbandRingingRuntimeDisable = false;
+            mForceScoAudio = false;
+            mAudioRouteAllowed = true;
+            mMaxHeadsetConnections = 1;
+            mVoiceRecognitionStarted = false;
+            mVirtualCallStarted = false;
+            if (mDialingOutTimeoutEvent != null) {
+                mStateMachinesThread.getThreadHandler().removeCallbacks(mDialingOutTimeoutEvent);
+                mDialingOutTimeoutEvent = null;
+            }
+            if (mVoiceRecognitionTimeoutEvent != null) {
+                mStateMachinesThread.getThreadHandler()
+                        .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+                mVoiceRecognitionTimeoutEvent = null;
+                if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
+                    mSystemInterface.getVoiceRecognitionWakeLock().release();
+                }
+            }
+            // Step 5: Destroy state machines
+            for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
+                HeadsetObjectsFactory.getInstance().destroyStateMachine(stateMachine);
+            }
+            mStateMachines.clear();
+        }
+        // Step 4: Destroy native interface
+        mNativeInterface.cleanup();
+        // Step 3: Destroy system interface
+        mSystemInterface.stop();
+        // Step 2: Stop handler thread
+        mStateMachinesThread.quitSafely();
+        mStateMachinesThread = null;
+        // Step 1: Clear
+        mAdapterService = null;
+        return true;
+    }
+
+    @Override
+    protected void cleanup() {
+        Log.i(TAG, "cleanup");
+        if (!mCreated) {
+            Log.w(TAG, "cleanup() called before create()");
+        }
+        mCreated = false;
+    }
+
+    /**
+     * Checks if this service object is able to accept binder calls
+     *
+     * @return True if the object can accept binder calls, False otherwise
+     */
+    public boolean isAlive() {
+        return isAvailable() && mCreated && mStarted;
+    }
+
+    /**
+     * Get the {@link Looper} for the state machine thread. This is used in testing and helper
+     * objects
+     *
+     * @return {@link Looper} for the state machine thread
+     */
+    @VisibleForTesting
+    public Looper getStateMachinesThreadLooper() {
+        return mStateMachinesThread.getLooper();
+    }
+
+    interface StateMachineTask {
+        void execute(HeadsetStateMachine stateMachine);
+    }
+
+    private boolean doForStateMachine(BluetoothDevice device, StateMachineTask task) {
+        synchronized (mStateMachines) {
+            HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                return false;
+            }
+            task.execute(stateMachine);
         }
         return true;
     }
 
-    protected boolean cleanup() {
-        if (mStateMachine != null) {
-            mStateMachine.cleanup();
+    private void doForEachConnectedStateMachine(StateMachineTask task) {
+        synchronized (mStateMachines) {
+            for (BluetoothDevice device : getConnectedDevices()) {
+                task.execute(mStateMachines.get(device));
+            }
         }
-        clearHeadsetService();
-        return true;
+    }
+
+    void onDeviceStateChanged(HeadsetDeviceState deviceState) {
+        doForEachConnectedStateMachine(
+                stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
+                        deviceState));
+    }
+
+    /**
+     * Handle messages from native (JNI) to Java. This needs to be synchronized to avoid posting
+     * messages to state machine before start() is done
+     *
+     * @param stackEvent event from native stack
+     */
+    void messageFromNative(HeadsetStackEvent stackEvent) {
+        Objects.requireNonNull(stackEvent.device,
+                "Device should never be null, event: " + stackEvent);
+        synchronized (mStateMachines) {
+            HeadsetStateMachine stateMachine = mStateMachines.get(stackEvent.device);
+            if (stackEvent.type == HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
+                switch (stackEvent.valueInt) {
+                    case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
+                    case HeadsetHalConstants.CONNECTION_STATE_CONNECTING: {
+                        // Create new state machine if none is found
+                        if (stateMachine == null) {
+                            stateMachine = HeadsetObjectsFactory.getInstance()
+                                    .makeStateMachine(stackEvent.device,
+                                            mStateMachinesThread.getLooper(), this, mAdapterService,
+                                            mNativeInterface, mSystemInterface);
+                            mStateMachines.put(stackEvent.device, stateMachine);
+                        }
+                        break;
+                    }
+                }
+            }
+            if (stateMachine == null) {
+                throw new IllegalStateException(
+                        "State machine not found for stack event: " + stackEvent);
+            }
+            stateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT, stackEvent);
+        }
     }
 
     private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
-                mStateMachine.sendMessage(HeadsetStateMachine.INTENT_BATTERY_CHANGED, intent);
-            } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
-                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
-                if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
-                    mStateMachine.sendMessage(
-                            HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, intent);
+            if (action == null) {
+                Log.w(TAG, "mHeadsetReceiver, action is null");
+                return;
+            }
+            switch (action) {
+                case Intent.ACTION_BATTERY_CHANGED: {
+                    int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+                    int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+                    if (batteryLevel < 0 || scale <= 0) {
+                        Log.e(TAG, "Bad Battery Changed intent: batteryLevel=" + batteryLevel
+                                + ", scale=" + scale);
+                        return;
+                    }
+                    batteryLevel = batteryLevel * 5 / scale;
+                    mSystemInterface.getHeadsetPhoneState().setCindBatteryCharge(batteryLevel);
+                    break;
                 }
-            } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
-                int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-                        BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
-                if (requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
-                    Log.v(TAG, "Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY");
-                    mStateMachine.handleAccessPermissionResult(intent);
+                case AudioManager.VOLUME_CHANGED_ACTION: {
+                    int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+                    if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) {
+                        doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage(
+                                HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, intent));
+                    }
+                    break;
                 }
+                case BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY: {
+                    int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+                            BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
+                    BluetoothDevice device =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    logD("Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, device=" + device
+                            + ", type=" + requestType);
+                    if (requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
+                        synchronized (mStateMachines) {
+                            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+                            if (stateMachine == null) {
+                                Log.wtfStack(TAG, "Cannot find state machine for " + device);
+                                return;
+                            }
+                            stateMachine.sendMessage(
+                                    HeadsetStateMachine.INTENT_CONNECTION_ACCESS_REPLY, intent);
+                        }
+                    }
+                    break;
+                }
+                case BluetoothDevice.ACTION_BOND_STATE_CHANGED: {
+                    int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                            BluetoothDevice.ERROR);
+                    BluetoothDevice device = Objects.requireNonNull(
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE),
+                            "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
+                    logD("Bond state changed for device: " + device + " state: " + state);
+                    if (state != BluetoothDevice.BOND_NONE) {
+                        break;
+                    }
+                    synchronized (mStateMachines) {
+                        HeadsetStateMachine stateMachine = mStateMachines.get(device);
+                        if (stateMachine == null) {
+                            break;
+                        }
+                        if (stateMachine.getConnectionState()
+                                != BluetoothProfile.STATE_DISCONNECTED) {
+                            break;
+                        }
+                        removeStateMachine(device);
+                    }
+                    break;
+                }
+                default:
+                    Log.w(TAG, "Unknown action " + action);
             }
         }
     };
@@ -114,139 +397,172 @@
     /**
      * Handlers for incoming service calls
      */
-    private static class BluetoothHeadsetBinder
-            extends IBluetoothHeadset.Stub implements IProfileServiceBinder {
-        private HeadsetService mService;
+    private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub
+            implements IProfileServiceBinder {
+        private volatile HeadsetService mService;
 
-        public BluetoothHeadsetBinder(HeadsetService svc) {
+        BluetoothHeadsetBinder(HeadsetService svc) {
             mService = svc;
         }
-        public boolean cleanup() {
+
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
         private HeadsetService getService() {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
+            final HeadsetService service = mService;
+            if (!Utils.checkCallerAllowManagedProfiles(service)) {
                 Log.w(TAG, "Headset call not allowed for non-active user");
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
+            if (service == null) {
+                Log.w(TAG, "Service is null");
+                return null;
             }
-            return null;
+            if (!service.isAlive()) {
+                Log.w(TAG, "Service is not alive");
+                return null;
+            }
+            return service;
         }
 
+        @Override
         public boolean connect(BluetoothDevice device) {
             HeadsetService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.connect(device);
         }
 
+        @Override
         public boolean disconnect(BluetoothDevice device) {
             HeadsetService service = getService();
-            if (service == null) return false;
-            if (DBG) Log.d(TAG, "disconnect in HeadsetService");
+            if (service == null) {
+                return false;
+            }
             return service.disconnect(device);
         }
 
+        @Override
         public List<BluetoothDevice> getConnectedDevices() {
             HeadsetService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getConnectedDevices();
         }
 
+        @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
             HeadsetService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getDevicesMatchingConnectionStates(states);
         }
 
+        @Override
         public int getConnectionState(BluetoothDevice device) {
             HeadsetService service = getService();
-            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
             return service.getConnectionState(device);
         }
 
+        @Override
         public boolean setPriority(BluetoothDevice device, int priority) {
             HeadsetService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPriority(device, priority);
         }
 
+        @Override
         public int getPriority(BluetoothDevice device) {
             HeadsetService service = getService();
-            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
+            if (service == null) {
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
             return service.getPriority(device);
         }
 
+        @Override
         public boolean startVoiceRecognition(BluetoothDevice device) {
             HeadsetService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.startVoiceRecognition(device);
         }
 
+        @Override
         public boolean stopVoiceRecognition(BluetoothDevice device) {
             HeadsetService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.stopVoiceRecognition(device);
         }
 
+        @Override
         public boolean isAudioOn() {
             HeadsetService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isAudioOn();
         }
 
+        @Override
         public boolean isAudioConnected(BluetoothDevice device) {
             HeadsetService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isAudioConnected(device);
         }
 
-        public int getBatteryUsageHint(BluetoothDevice device) {
-            HeadsetService service = getService();
-            if (service == null) return 0;
-            return service.getBatteryUsageHint(device);
-        }
-
-        public boolean acceptIncomingConnect(BluetoothDevice device) {
-            HeadsetService service = getService();
-            if (service == null) return false;
-            return service.acceptIncomingConnect(device);
-        }
-
-        public boolean rejectIncomingConnect(BluetoothDevice device) {
-            HeadsetService service = getService();
-            if (service == null) return false;
-            return service.rejectIncomingConnect(device);
-        }
-
+        @Override
         public int getAudioState(BluetoothDevice device) {
             HeadsetService service = getService();
-            if (service == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+            if (service == null) {
+                return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+            }
             return service.getAudioState(device);
         }
 
+        @Override
         public boolean connectAudio() {
             HeadsetService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.connectAudio();
         }
 
+        @Override
         public boolean disconnectAudio() {
             HeadsetService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.disconnectAudio();
         }
 
+        @Override
         public void setAudioRouteAllowed(boolean allowed) {
             HeadsetService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setAudioRouteAllowed(allowed);
         }
 
+        @Override
         public boolean getAudioRouteAllowed() {
             HeadsetService service = getService();
             if (service != null) {
@@ -255,40 +571,56 @@
             return false;
         }
 
+        @Override
         public void setForceScoAudio(boolean forced) {
             HeadsetService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.setForceScoAudio(forced);
         }
 
-        public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
+        @Override
+        public boolean startScoUsingVirtualVoiceCall() {
             HeadsetService service = getService();
-            if (service == null) return false;
-            return service.startScoUsingVirtualVoiceCall(device);
+            if (service == null) {
+                return false;
+            }
+            return service.startScoUsingVirtualVoiceCall();
         }
 
-        public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
+        @Override
+        public boolean stopScoUsingVirtualVoiceCall() {
             HeadsetService service = getService();
-            if (service == null) return false;
-            return service.stopScoUsingVirtualVoiceCall(device);
+            if (service == null) {
+                return false;
+            }
+            return service.stopScoUsingVirtualVoiceCall();
         }
 
-        public void phoneStateChanged(
-                int numActive, int numHeld, int callState, String number, int type) {
+        @Override
+        public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
+                int type) {
             HeadsetService service = getService();
-            if (service == null) return;
-            service.phoneStateChanged(numActive, numHeld, callState, number, type);
+            if (service == null) {
+                return;
+            }
+            service.phoneStateChanged(numActive, numHeld, callState, number, type, false);
         }
 
+        @Override
         public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
                 String number, int type) {
             HeadsetService service = getService();
-            if (service == null) return;
+            if (service == null) {
+                return;
+            }
             service.clccResponse(index, direction, status, mode, mpty, number, type);
         }
 
-        public boolean sendVendorSpecificResultCode(
-                BluetoothDevice device, String command, String arg) {
+        @Override
+        public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
+                String arg) {
             HeadsetService service = getService();
             if (service == null) {
                 return false;
@@ -296,323 +628,1186 @@
             return service.sendVendorSpecificResultCode(device, command, arg);
         }
 
-        public boolean enableWBS() {
+        @Override
+        public boolean setActiveDevice(BluetoothDevice device) {
             HeadsetService service = getService();
-            if (service == null) return false;
-            return service.enableWBS();
+            if (service == null) {
+                return false;
+            }
+            return service.setActiveDevice(device);
         }
 
-        public boolean disableWBS() {
+        @Override
+        public BluetoothDevice getActiveDevice() {
             HeadsetService service = getService();
-            if (service == null) return false;
-            return service.disableWBS();
+            if (service == null) {
+                return null;
+            }
+            return service.getActiveDevice();
         }
 
-        public void bindResponse(int ind_id, boolean ind_status) {
+        @Override
+        public boolean isInbandRingingEnabled() {
             HeadsetService service = getService();
-            if (service == null) return;
-            service.bindResponse(ind_id, ind_status);
+            if (service == null) {
+                return false;
+            }
+            return service.isInbandRingingEnabled();
         }
-    };
+    }
 
     // API methods
     public static synchronized HeadsetService getHeadsetService() {
-        if (sHeadsetService != null && sHeadsetService.isAvailable()) {
-            if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService);
-            return sHeadsetService;
+        if (sHeadsetService == null) {
+            Log.w(TAG, "getHeadsetService(): service is NULL");
+            return null;
         }
-        if (DBG) {
-            if (sHeadsetService == null) {
-                Log.d(TAG, "getHeadsetService(): service is NULL");
-            } else if (!(sHeadsetService.isAvailable())) {
-                Log.d(TAG, "getHeadsetService(): service is not available");
-            }
+        if (!sHeadsetService.isAvailable()) {
+            Log.w(TAG, "getHeadsetService(): service is not available");
+            return null;
         }
-        return null;
+        logD("getHeadsetService(): returning " + sHeadsetService);
+        return sHeadsetService;
     }
 
     private static synchronized void setHeadsetService(HeadsetService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService);
-            sHeadsetService = instance;
-        } else {
-            if (DBG) {
-                if (sHeadsetService == null) {
-                    Log.d(TAG, "setHeadsetService(): service not available");
-                } else if (!sHeadsetService.isAvailable()) {
-                    Log.d(TAG, "setHeadsetService(): service is cleaning up");
-                }
-            }
-        }
-    }
-
-    private static synchronized void clearHeadsetService() {
-        sHeadsetService = null;
+        logD("setHeadsetService(): set to: " + instance);
+        sHeadsetService = instance;
     }
 
     public boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-        Log.d(TAG, "connect: device=" + device);
         if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
-            Log.w(TAG, "connect: PRIORITY_OFF, device=" + device);
+            Log.w(TAG, "connect: PRIORITY_OFF, device=" + device + ", " + Utils.getUidPidString());
             return false;
         }
-        int connectionState = mStateMachine.getConnectionState(device);
-        if (connectionState == BluetoothProfile.STATE_CONNECTED
-                || connectionState == BluetoothProfile.STATE_CONNECTING) {
-            Log.w(TAG,
-                    "connect: already connected/connecting, connectionState=" + connectionState
-                            + ", device=" + device);
+        ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
+        if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
+            Log.e(TAG, "connect: Cannot connect to " + device + ": no headset UUID, "
+                    + Utils.getUidPidString());
             return false;
         }
-        mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device);
+        synchronized (mStateMachines) {
+            Log.i(TAG, "connect: device=" + device + ", " + Utils.getUidPidString());
+            HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                stateMachine = HeadsetObjectsFactory.getInstance()
+                        .makeStateMachine(device, mStateMachinesThread.getLooper(), this,
+                                mAdapterService, mNativeInterface, mSystemInterface);
+                mStateMachines.put(device, stateMachine);
+            }
+            int connectionState = stateMachine.getConnectionState();
+            if (connectionState == BluetoothProfile.STATE_CONNECTED
+                    || connectionState == BluetoothProfile.STATE_CONNECTING) {
+                Log.w(TAG, "connect: device " + device
+                        + " is already connected/connecting, connectionState=" + connectionState);
+                return false;
+            }
+            List<BluetoothDevice> connectingConnectedDevices =
+                    getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+            boolean disconnectExisting = false;
+            if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) {
+                // When there is maximum one device, we automatically disconnect the current one
+                if (mMaxHeadsetConnections == 1) {
+                    disconnectExisting = true;
+                } else {
+                    Log.w(TAG, "Max connection has reached, rejecting connection to " + device);
+                    return false;
+                }
+            }
+            if (disconnectExisting) {
+                for (BluetoothDevice connectingConnectedDevice : connectingConnectedDevices) {
+                    disconnect(connectingConnectedDevice);
+                }
+                setActiveDevice(null);
+            }
+            stateMachine.sendMessage(HeadsetStateMachine.CONNECT, device);
+        }
         return true;
     }
 
     boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-        Log.d(TAG, "disconnect: device=" + device);
-        int connectionState = mStateMachine.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED
-                && connectionState != BluetoothProfile.STATE_CONNECTING) {
-            Log.w(TAG,
-                    "disconnect: not connected/connecting, connectionState=" + connectionState
-                            + ", device=" + device);
-            return false;
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        Log.i(TAG, "disconnect: device=" + device + ", " + Utils.getUidPidString());
+        synchronized (mStateMachines) {
+            HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                Log.w(TAG, "disconnect: device " + device + " not ever connected/connecting");
+                return false;
+            }
+            int connectionState = stateMachine.getConnectionState();
+            if (connectionState != BluetoothProfile.STATE_CONNECTED
+                    && connectionState != BluetoothProfile.STATE_CONNECTING) {
+                Log.w(TAG, "disconnect: device " + device
+                        + " not connected/connecting, connectionState=" + connectionState);
+                return false;
+            }
+            stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device);
         }
-        mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device);
         return true;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getConnectedDevices();
+        ArrayList<BluetoothDevice> devices = new ArrayList<>();
+        synchronized (mStateMachines) {
+            for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
+                if (stateMachine.getConnectionState() == BluetoothProfile.STATE_CONNECTED) {
+                    devices.add(stateMachine.getDevice());
+                }
+            }
+        }
+        return devices;
     }
 
-    private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+    /**
+     * Same as the API method {@link BluetoothHeadset#getDevicesMatchingConnectionStates(int[])}
+     *
+     * @param states an array of states from {@link BluetoothProfile}
+     * @return a list of devices matching the array of connection states
+     */
+    @VisibleForTesting
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getDevicesMatchingConnectionStates(states);
+        ArrayList<BluetoothDevice> devices = new ArrayList<>();
+        if (states == null) {
+            return devices;
+        }
+        final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+        if (bondedDevices == null) {
+            return devices;
+        }
+        synchronized (mStateMachines) {
+            for (BluetoothDevice device : bondedDevices) {
+                final ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
+                if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
+                    continue;
+                }
+                int connectionState = getConnectionState(device);
+                for (int state : states) {
+                    if (connectionState == state) {
+                        devices.add(device);
+                        break;
+                    }
+                }
+            }
+        }
+        return devices;
     }
 
     public int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.getConnectionState(device);
+        synchronized (mStateMachines) {
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+            return stateMachine.getConnectionState();
+        }
     }
 
     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);
+        Log.i(TAG, "setPriority: device=" + device + ", priority=" + priority + ", "
+                + Utils.getUidPidString());
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        int priority = Settings.Global.getInt(getContentResolver(),
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return Settings.Global.getInt(getContentResolver(),
                 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
                 BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
     }
 
     boolean startVoiceRecognition(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        int connectionState = mStateMachine.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED
-                && connectionState != BluetoothProfile.STATE_CONNECTING) {
-            return false;
+        Log.i(TAG, "startVoiceRecognition: device=" + device + ", " + Utils.getUidPidString());
+        synchronized (mStateMachines) {
+            // TODO(b/79660380): Workaround in case voice recognition was not terminated properly
+            if (mVoiceRecognitionStarted) {
+                boolean status = stopVoiceRecognition(mActiveDevice);
+                Log.w(TAG, "startVoiceRecognition: voice recognition is still active, just called "
+                        + "stopVoiceRecognition, returned " + status + " on " + mActiveDevice
+                        + ", please try again");
+                mVoiceRecognitionStarted = false;
+                return false;
+            }
+            if (!isAudioModeIdle()) {
+                Log.w(TAG, "startVoiceRecognition: audio mode not idle, active device is "
+                        + mActiveDevice);
+                return false;
+            }
+            // Audio should not be on when no audio mode is active
+            if (isAudioOn()) {
+                // Disconnect audio so that API user can try later
+                boolean status = disconnectAudio();
+                Log.w(TAG, "startVoiceRecognition: audio is still active, please wait for audio to"
+                        + " be disconnected, disconnectAudio() returned " + status
+                        + ", active device is " + mActiveDevice);
+                return false;
+            }
+            if (device == null) {
+                Log.i(TAG, "device is null, use active device " + mActiveDevice + " instead");
+                device = mActiveDevice;
+            }
+            boolean pendingRequestByHeadset = false;
+            if (mVoiceRecognitionTimeoutEvent != null) {
+                if (!mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice.equals(device)) {
+                    // TODO(b/79660380): Workaround when target device != requesting device
+                    Log.w(TAG, "startVoiceRecognition: device " + device
+                            + " is not the same as requesting device "
+                            + mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice
+                            + ", fall back to requesting device");
+                    device = mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice;
+                }
+                mStateMachinesThread.getThreadHandler()
+                        .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+                mVoiceRecognitionTimeoutEvent = null;
+                if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
+                    mSystemInterface.getVoiceRecognitionWakeLock().release();
+                }
+                pendingRequestByHeadset = true;
+            }
+            if (!Objects.equals(device, mActiveDevice) && !setActiveDevice(device)) {
+                Log.w(TAG, "startVoiceRecognition: failed to set " + device + " as active");
+                return false;
+            }
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                Log.w(TAG, "startVoiceRecognition: " + device + " is never connected");
+                return false;
+            }
+            int connectionState = stateMachine.getConnectionState();
+            if (connectionState != BluetoothProfile.STATE_CONNECTED
+                    && connectionState != BluetoothProfile.STATE_CONNECTING) {
+                Log.w(TAG, "startVoiceRecognition: " + device + " is not connected or connecting");
+                return false;
+            }
+            mVoiceRecognitionStarted = true;
+            if (pendingRequestByHeadset) {
+                stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_RESULT,
+                        1 /* success */, 0, device);
+            } else {
+                stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START, device);
+            }
+            stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
         }
-        mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START);
         return true;
     }
 
     boolean stopVoiceRecognition(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        // It seem that we really need to check the AudioOn state.
-        // But since we allow startVoiceRecognition in STATE_CONNECTED and
-        // STATE_CONNECTING state, we do these 2 in this method
-        int connectionState = mStateMachine.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED
-                && connectionState != BluetoothProfile.STATE_CONNECTING) {
-            return false;
+        Log.i(TAG, "stopVoiceRecognition: device=" + device + ", " + Utils.getUidPidString());
+        synchronized (mStateMachines) {
+            if (!Objects.equals(mActiveDevice, device)) {
+                Log.w(TAG, "startVoiceRecognition: requested device " + device
+                        + " is not active, use active device " + mActiveDevice + " instead");
+                device = mActiveDevice;
+            }
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                Log.w(TAG, "stopVoiceRecognition: " + device + " is never connected");
+                return false;
+            }
+            int connectionState = stateMachine.getConnectionState();
+            if (connectionState != BluetoothProfile.STATE_CONNECTED
+                    && connectionState != BluetoothProfile.STATE_CONNECTING) {
+                Log.w(TAG, "stopVoiceRecognition: " + device + " is not connected or connecting");
+                return false;
+            }
+            if (!mVoiceRecognitionStarted) {
+                Log.w(TAG, "stopVoiceRecognition: voice recognition was not started");
+                return false;
+            }
+            mVoiceRecognitionStarted = false;
+            stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device);
+            stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device);
         }
-        mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP);
-        // TODO is this return correct when the voice recognition is not on?
         return true;
     }
 
     boolean isAudioOn() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.isAudioOn();
+        return getNonIdleAudioDevices().size() > 0;
     }
 
     boolean isAudioConnected(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mStateMachine.isAudioConnected(device);
-    }
-
-    int getBatteryUsageHint(BluetoothDevice device) {
-        // TODO(BT) ask for BT stack support?
-        return 0;
-    }
-
-    boolean acceptIncomingConnect(BluetoothDevice device) {
-        // TODO(BT) remove it if stack does access control
-        return false;
-    }
-
-    boolean rejectIncomingConnect(BluetoothDevice device) {
-        // TODO(BT) remove it if stack does access control
-        return false;
+        synchronized (mStateMachines) {
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                return false;
+            }
+            return stateMachine.getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED;
+        }
     }
 
     int getAudioState(BluetoothDevice device) {
-        return mStateMachine.getAudioState(device);
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        synchronized (mStateMachines) {
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+            }
+            return stateMachine.getAudioState();
+        }
     }
 
     public void setAudioRouteAllowed(boolean allowed) {
-        mStateMachine.setAudioRouteAllowed(allowed);
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        Log.i(TAG, "setAudioRouteAllowed: allowed=" + allowed + ", " + Utils.getUidPidString());
+        mAudioRouteAllowed = allowed;
+        mNativeInterface.setScoAllowed(allowed);
     }
 
     public boolean getAudioRouteAllowed() {
-        return mStateMachine.getAudioRouteAllowed();
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return mAudioRouteAllowed;
     }
 
     public void setForceScoAudio(boolean forced) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        mStateMachine.setForceScoAudio(forced);
+        Log.i(TAG, "setForceScoAudio: forced=" + forced + ", " + Utils.getUidPidString());
+        mForceScoAudio = forced;
+    }
+
+    @VisibleForTesting
+    public boolean getForceScoAudio() {
+        return mForceScoAudio;
+    }
+
+    /**
+     * Get first available device for SCO audio
+     *
+     * @return first connected headset device
+     */
+    @VisibleForTesting
+    @Nullable
+    public BluetoothDevice getFirstConnectedAudioDevice() {
+        ArrayList<HeadsetStateMachine> stateMachines = new ArrayList<>();
+        synchronized (mStateMachines) {
+            List<BluetoothDevice> availableDevices =
+                    getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+            for (BluetoothDevice device : availableDevices) {
+                final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+                if (stateMachine == null) {
+                    continue;
+                }
+                stateMachines.add(stateMachine);
+            }
+        }
+        stateMachines.sort(Comparator.comparingLong(HeadsetStateMachine::getConnectingTimestampMs));
+        if (stateMachines.size() > 0) {
+            return stateMachines.get(0).getDevice();
+        }
+        return null;
+    }
+
+    /**
+     * Set the active device.
+     *
+     * @param device the active device
+     * @return true on success, otherwise false
+     */
+    public boolean setActiveDevice(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        Log.i(TAG, "setActiveDevice: device=" + device + ", " + Utils.getUidPidString());
+        synchronized (mStateMachines) {
+            if (device == null) {
+                // Clear the active device
+                if (mVoiceRecognitionStarted) {
+                    if (!stopVoiceRecognition(mActiveDevice)) {
+                        Log.w(TAG, "setActiveDevice: fail to stopVoiceRecognition from "
+                                + mActiveDevice);
+                    }
+                }
+                if (mVirtualCallStarted) {
+                    if (!stopScoUsingVirtualVoiceCall()) {
+                        Log.w(TAG, "setActiveDevice: fail to stopScoUsingVirtualVoiceCall from "
+                                + mActiveDevice);
+                    }
+                }
+                if (getAudioState(mActiveDevice) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                    if (!disconnectAudio(mActiveDevice)) {
+                        Log.w(TAG, "setActiveDevice: disconnectAudio failed on " + mActiveDevice);
+                    }
+                }
+                mActiveDevice = null;
+                broadcastActiveDevice(null);
+                return true;
+            }
+            if (device.equals(mActiveDevice)) {
+                Log.i(TAG, "setActiveDevice: device " + device + " is already active");
+                return true;
+            }
+            if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
+                Log.e(TAG, "setActiveDevice: Cannot set " + device
+                        + " as active, device is not connected");
+                return false;
+            }
+            if (!mNativeInterface.setActiveDevice(device)) {
+                Log.e(TAG, "setActiveDevice: Cannot set " + device + " as active in native layer");
+                return false;
+            }
+            BluetoothDevice previousActiveDevice = mActiveDevice;
+            mActiveDevice = device;
+            if (getAudioState(previousActiveDevice) != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                if (!disconnectAudio(previousActiveDevice)) {
+                    Log.e(TAG, "setActiveDevice: fail to disconnectAudio from "
+                            + previousActiveDevice);
+                    mActiveDevice = previousActiveDevice;
+                    mNativeInterface.setActiveDevice(previousActiveDevice);
+                    return false;
+                }
+                broadcastActiveDevice(mActiveDevice);
+            } else if (shouldPersistAudio()) {
+                broadcastActiveDevice(mActiveDevice);
+                if (!connectAudio(mActiveDevice)) {
+                    Log.e(TAG, "setActiveDevice: fail to connectAudio to " + mActiveDevice);
+                    mActiveDevice = previousActiveDevice;
+                    mNativeInterface.setActiveDevice(previousActiveDevice);
+                    return false;
+                }
+            } else {
+                broadcastActiveDevice(mActiveDevice);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Get the active device.
+     *
+     * @return the active device or null if no device is active
+     */
+    public BluetoothDevice getActiveDevice() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        synchronized (mStateMachines) {
+            return mActiveDevice;
+        }
     }
 
     boolean connectAudio() {
-        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (!mStateMachine.isConnected()) {
-            Log.w(TAG, "connectAudio: profile not connected");
-            return false;
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        synchronized (mStateMachines) {
+            BluetoothDevice device = mActiveDevice;
+            if (device == null) {
+                Log.w(TAG, "connectAudio: no active device, " + Utils.getUidPidString());
+                return false;
+            }
+            return connectAudio(device);
         }
-        if (mStateMachine.isAudioOn()) {
-            Log.w(TAG, "connectAudio: audio is already ON");
-            return false;
+    }
+
+    boolean connectAudio(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString());
+        synchronized (mStateMachines) {
+            if (!isScoAcceptable(device)) {
+                Log.w(TAG, "connectAudio, rejected SCO request to " + device);
+                return false;
+            }
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                Log.w(TAG, "connectAudio: device " + device + " was never connected/connecting");
+                return false;
+            }
+            if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
+                Log.w(TAG, "connectAudio: profile not connected");
+                return false;
+            }
+            if (stateMachine.getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                logD("connectAudio: audio is not idle for device " + device);
+                return true;
+            }
+            if (isAudioOn()) {
+                Log.w(TAG, "connectAudio: audio is not idle, current audio devices are "
+                        + Arrays.toString(getNonIdleAudioDevices().toArray()));
+                return false;
+            }
+            stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
         }
-        mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO);
         return true;
     }
 
+    private List<BluetoothDevice> getNonIdleAudioDevices() {
+        ArrayList<BluetoothDevice> devices = new ArrayList<>();
+        synchronized (mStateMachines) {
+            for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
+                if (stateMachine.getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                    devices.add(stateMachine.getDevice());
+                }
+            }
+        }
+        return devices;
+    }
+
     boolean disconnectAudio() {
-        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (!mStateMachine.isAudioOn()) {
-            return false;
-        }
-        mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO);
-        return true;
-    }
-
-    boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
-        /* Do not ignore request if HSM state is still Disconnected or
-           Pending, it will be processed when transitioned to Connected */
-        mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device);
-        return true;
-    }
-
-    boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
-        int connectionState = mStateMachine.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED
-                && connectionState != BluetoothProfile.STATE_CONNECTING) {
-            return false;
-        }
-        mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device);
-        return true;
-    }
-
-    private void phoneStateChanged(
-            int numActive, int numHeld, int callState, String number, int type) {
-        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
-        Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED);
-        msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type);
-        msg.arg1 = 0; // false
-        mStateMachine.sendMessage(msg);
-    }
-
-    private void clccResponse(
-            int index, int direction, int status, int mode, boolean mpty, String number, int type) {
-        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
-        mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE,
-                new HeadsetClccResponse(index, direction, status, mode, mpty, number, type));
-    }
-
-    private boolean sendVendorSpecificResultCode(
-            BluetoothDevice device, String command, String arg) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        int connectionState = mStateMachine.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED) {
-            return false;
-        }
-        // Currently we support only "+ANDROID".
-        if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) {
-            Log.w(TAG, "Disallowed unsolicited result code command: " + command);
-            return false;
-        }
-        mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE,
-                new HeadsetVendorSpecificResultCode(device, command, arg));
-        return true;
-    }
-
-    boolean enableWBS() {
-        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (!mStateMachine.isConnected()) {
-            return false;
-        }
-        if (mStateMachine.isAudioOn()) {
-            return false;
-        }
-
-        for (BluetoothDevice device : getConnectedDevices()) {
-            mStateMachine.sendMessage(HeadsetStateMachine.ENABLE_WBS, device);
-        }
-
-        return true;
-    }
-
-    boolean disableWBS() {
-        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (!mStateMachine.isConnected()) {
-            return false;
-        }
-        if (mStateMachine.isAudioOn()) {
-            return false;
-        }
-        for (BluetoothDevice device : getConnectedDevices()) {
-            mStateMachine.sendMessage(HeadsetStateMachine.DISABLE_WBS, device);
-        }
-        return true;
-    }
-
-    private boolean bindResponse(int ind_id, boolean ind_status) {
-        for (BluetoothDevice device : getConnectedDevices()) {
-            int connectionState = mStateMachine.getConnectionState(device);
-            if (connectionState != BluetoothProfile.STATE_CONNECTED
-                    && connectionState != BluetoothProfile.STATE_CONNECTING) {
-                continue;
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        boolean result = false;
+        synchronized (mStateMachines) {
+            for (BluetoothDevice device : getNonIdleAudioDevices()) {
+                if (disconnectAudio(device)) {
+                    result = true;
+                } else {
+                    Log.e(TAG, "disconnectAudio() from " + device + " failed");
+                }
             }
-            if (DBG) Log.d("Bind Response sent for", device.getAddress());
-            Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.BIND_RESPONSE);
-            msg.obj = device;
-            msg.arg1 = ind_id;
-            msg.arg2 = ind_status ? 1 : 0;
-            mStateMachine.sendMessage(msg);
+        }
+        if (!result) {
+            logD("disconnectAudio() no active audio connection");
+        }
+        return result;
+    }
+
+    boolean disconnectAudio(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        synchronized (mStateMachines) {
+            Log.i(TAG, "disconnectAudio: device=" + device + ", " + Utils.getUidPidString());
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                Log.w(TAG, "disconnectAudio: device " + device + " was never connected/connecting");
+                return false;
+            }
+            if (stateMachine.getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                Log.w(TAG, "disconnectAudio, audio is already disconnected for " + device);
+                return false;
+            }
+            stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device);
+        }
+        return true;
+    }
+
+    boolean isVirtualCallStarted() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        synchronized (mStateMachines) {
+            return mVirtualCallStarted;
+        }
+    }
+
+    private boolean startScoUsingVirtualVoiceCall() {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        Log.i(TAG, "startScoUsingVirtualVoiceCall: " + Utils.getUidPidString());
+        synchronized (mStateMachines) {
+            // TODO(b/79660380): Workaround in case voice recognition was not terminated properly
+            if (mVoiceRecognitionStarted) {
+                boolean status = stopVoiceRecognition(mActiveDevice);
+                Log.w(TAG, "startScoUsingVirtualVoiceCall: voice recognition is still active, "
+                        + "just called stopVoiceRecognition, returned " + status + " on "
+                        + mActiveDevice + ", please try again");
+                mVoiceRecognitionStarted = false;
+                return false;
+            }
+            if (!isAudioModeIdle()) {
+                Log.w(TAG, "startScoUsingVirtualVoiceCall: audio mode not idle, active device is "
+                        + mActiveDevice);
+                return false;
+            }
+            // Audio should not be on when no audio mode is active
+            if (isAudioOn()) {
+                // Disconnect audio so that API user can try later
+                boolean status = disconnectAudio();
+                Log.w(TAG, "startScoUsingVirtualVoiceCall: audio is still active, please wait for "
+                        + "audio to be disconnected, disconnectAudio() returned " + status
+                        + ", active device is " + mActiveDevice);
+                return false;
+            }
+            if (mActiveDevice == null) {
+                Log.w(TAG, "startScoUsingVirtualVoiceCall: no active device");
+                return false;
+            }
+            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);
             return true;
         }
-        return false;
+    }
+
+    boolean stopScoUsingVirtualVoiceCall() {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        Log.i(TAG, "stopScoUsingVirtualVoiceCall: " + Utils.getUidPidString());
+        synchronized (mStateMachines) {
+            // 1. Check if virtual call has already started
+            if (!mVirtualCallStarted) {
+                Log.w(TAG, "stopScoUsingVirtualVoiceCall: virtual call not started");
+                return false;
+            }
+            mVirtualCallStarted = false;
+            // 2. Send virtual phone state changed to close SCO
+            phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, true);
+        }
+        return true;
+    }
+
+    class DialingOutTimeoutEvent implements Runnable {
+        BluetoothDevice mDialingOutDevice;
+
+        DialingOutTimeoutEvent(BluetoothDevice fromDevice) {
+            mDialingOutDevice = fromDevice;
+        }
+
+        @Override
+        public void run() {
+            synchronized (mStateMachines) {
+                mDialingOutTimeoutEvent = null;
+                doForStateMachine(mDialingOutDevice, stateMachine -> stateMachine.sendMessage(
+                        HeadsetStateMachine.DIALING_OUT_RESULT, 0 /* fail */, 0,
+                        mDialingOutDevice));
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "DialingOutTimeoutEvent[" + mDialingOutDevice + "]";
+        }
+    }
+
+    /**
+     * Dial an outgoing call as requested by the remote device
+     *
+     * @param fromDevice remote device that initiated this dial out action
+     * @param dialNumber number to dial
+     * @return true on successful dial out
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) {
+        synchronized (mStateMachines) {
+            Log.i(TAG, "dialOutgoingCall: from " + fromDevice);
+            if (!isOnStateMachineThread()) {
+                Log.e(TAG, "dialOutgoingCall must be called from state machine thread");
+                return false;
+            }
+            if (mDialingOutTimeoutEvent != null) {
+                Log.e(TAG, "dialOutgoingCall, already dialing by " + mDialingOutTimeoutEvent);
+                return false;
+            }
+            if (isVirtualCallStarted()) {
+                if (!stopScoUsingVirtualVoiceCall()) {
+                    Log.e(TAG, "dialOutgoingCall failed to stop current virtual call");
+                    return false;
+                }
+            }
+            if (!setActiveDevice(fromDevice)) {
+                Log.e(TAG, "dialOutgoingCall failed to set active device to " + fromDevice);
+                return false;
+            }
+            Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+                    Uri.fromParts(PhoneAccount.SCHEME_TEL, dialNumber, null));
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivity(intent);
+            mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(fromDevice);
+            mStateMachinesThread.getThreadHandler()
+                    .postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
+            return true;
+        }
+    }
+
+    /**
+     * Check if any connected headset has started dialing calls
+     *
+     * @return true if some device has started dialing calls
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean hasDeviceInitiatedDialingOut() {
+        synchronized (mStateMachines) {
+            return mDialingOutTimeoutEvent != null;
+        }
+    }
+
+    class VoiceRecognitionTimeoutEvent implements Runnable {
+        BluetoothDevice mVoiceRecognitionDevice;
+
+        VoiceRecognitionTimeoutEvent(BluetoothDevice device) {
+            mVoiceRecognitionDevice = device;
+        }
+
+        @Override
+        public void run() {
+            synchronized (mStateMachines) {
+                if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
+                    mSystemInterface.getVoiceRecognitionWakeLock().release();
+                }
+                mVoiceRecognitionTimeoutEvent = null;
+                doForStateMachine(mVoiceRecognitionDevice, stateMachine -> stateMachine.sendMessage(
+                        HeadsetStateMachine.VOICE_RECOGNITION_RESULT, 0 /* fail */, 0,
+                        mVoiceRecognitionDevice));
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "VoiceRecognitionTimeoutEvent[" + mVoiceRecognitionDevice + "]";
+        }
+    }
+
+    boolean startVoiceRecognitionByHeadset(BluetoothDevice fromDevice) {
+        synchronized (mStateMachines) {
+            Log.i(TAG, "startVoiceRecognitionByHeadset: from " + fromDevice);
+            // TODO(b/79660380): Workaround in case voice recognition was not terminated properly
+            if (mVoiceRecognitionStarted) {
+                boolean status = stopVoiceRecognition(mActiveDevice);
+                Log.w(TAG, "startVoiceRecognitionByHeadset: voice recognition is still active, "
+                        + "just called stopVoiceRecognition, returned " + status + " on "
+                        + mActiveDevice + ", please try again");
+                mVoiceRecognitionStarted = false;
+                return false;
+            }
+            if (fromDevice == null) {
+                Log.e(TAG, "startVoiceRecognitionByHeadset: fromDevice is null");
+                return false;
+            }
+            if (!isAudioModeIdle()) {
+                Log.w(TAG, "startVoiceRecognitionByHeadset: audio mode not idle, active device is "
+                        + mActiveDevice);
+                return false;
+            }
+            // Audio should not be on when no audio mode is active
+            if (isAudioOn()) {
+                // Disconnect audio so that user can try later
+                boolean status = disconnectAudio();
+                Log.w(TAG, "startVoiceRecognitionByHeadset: audio is still active, please wait for"
+                        + " audio to be disconnected, disconnectAudio() returned " + status
+                        + ", active device is " + mActiveDevice);
+                return false;
+            }
+            // Do not start new request until the current one is finished or timeout
+            if (mVoiceRecognitionTimeoutEvent != null) {
+                Log.w(TAG, "startVoiceRecognitionByHeadset: failed request from " + fromDevice
+                        + ", already pending by " + mVoiceRecognitionTimeoutEvent);
+                return false;
+            }
+            if (!setActiveDevice(fromDevice)) {
+                Log.w(TAG, "startVoiceRecognitionByHeadset: failed to set " + fromDevice
+                        + " as active");
+                return false;
+            }
+            IDeviceIdleController deviceIdleController = IDeviceIdleController.Stub.asInterface(
+                    ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+            if (deviceIdleController == null) {
+                Log.w(TAG, "startVoiceRecognitionByHeadset: deviceIdleController is null, device="
+                        + fromDevice);
+                return false;
+            }
+            try {
+                deviceIdleController.exitIdle("voice-command");
+            } catch (RemoteException e) {
+                Log.w(TAG,
+                        "startVoiceRecognitionByHeadset: failed to exit idle, device=" + fromDevice
+                                + ", error=" + e.getMessage());
+                return false;
+            }
+            if (!mSystemInterface.activateVoiceRecognition()) {
+                Log.w(TAG, "startVoiceRecognitionByHeadset: failed request from " + fromDevice);
+                return false;
+            }
+            mVoiceRecognitionTimeoutEvent = new VoiceRecognitionTimeoutEvent(fromDevice);
+            mStateMachinesThread.getThreadHandler()
+                    .postDelayed(mVoiceRecognitionTimeoutEvent, sStartVrTimeoutMs);
+            if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
+                mSystemInterface.getVoiceRecognitionWakeLock().acquire(sStartVrTimeoutMs);
+            }
+            return true;
+        }
+    }
+
+    boolean stopVoiceRecognitionByHeadset(BluetoothDevice fromDevice) {
+        synchronized (mStateMachines) {
+            Log.i(TAG, "stopVoiceRecognitionByHeadset: from " + fromDevice);
+            if (!Objects.equals(fromDevice, mActiveDevice)) {
+                Log.w(TAG, "stopVoiceRecognitionByHeadset: " + fromDevice
+                        + " is not active, active device is " + mActiveDevice);
+                return false;
+            }
+            if (!mVoiceRecognitionStarted && mVoiceRecognitionTimeoutEvent == null) {
+                Log.w(TAG, "stopVoiceRecognitionByHeadset: voice recognition not started, device="
+                        + fromDevice);
+                return false;
+            }
+            if (mVoiceRecognitionTimeoutEvent != null) {
+                if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
+                    mSystemInterface.getVoiceRecognitionWakeLock().release();
+                }
+                mStateMachinesThread.getThreadHandler()
+                        .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+                mVoiceRecognitionTimeoutEvent = null;
+            }
+            if (mVoiceRecognitionStarted) {
+                if (!disconnectAudio()) {
+                    Log.w(TAG, "stopVoiceRecognitionByHeadset: failed to disconnect audio from "
+                            + fromDevice);
+                }
+                mVoiceRecognitionStarted = false;
+            }
+            if (!mSystemInterface.deactivateVoiceRecognition()) {
+                Log.w(TAG, "stopVoiceRecognitionByHeadset: failed request from " + fromDevice);
+                return false;
+            }
+            return true;
+        }
+    }
+
+    private void phoneStateChanged(int numActive, int numHeld, int callState, String number,
+            int type, boolean isVirtualCall) {
+        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
+        synchronized (mStateMachines) {
+            // Should stop all other audio mode in this case
+            if ((numActive + numHeld) > 0 || callState != HeadsetHalConstants.CALL_STATE_IDLE) {
+                if (!isVirtualCall && mVirtualCallStarted) {
+                    // stop virtual voice call if there is an incoming Telecom call update
+                    stopScoUsingVirtualVoiceCall();
+                }
+                if (mVoiceRecognitionStarted) {
+                    // stop voice recognition if there is any incoming call
+                    stopVoiceRecognition(mActiveDevice);
+                }
+            }
+            if (mDialingOutTimeoutEvent != null) {
+                // Send result to state machine when dialing starts
+                if (callState == HeadsetHalConstants.CALL_STATE_DIALING) {
+                    mStateMachinesThread.getThreadHandler()
+                            .removeCallbacks(mDialingOutTimeoutEvent);
+                    doForStateMachine(mDialingOutTimeoutEvent.mDialingOutDevice,
+                            stateMachine -> stateMachine.sendMessage(
+                                    HeadsetStateMachine.DIALING_OUT_RESULT, 1 /* success */, 0,
+                                    mDialingOutTimeoutEvent.mDialingOutDevice));
+                } else if (callState == HeadsetHalConstants.CALL_STATE_ACTIVE
+                        || callState == HeadsetHalConstants.CALL_STATE_IDLE) {
+                    // Clear the timeout event when the call is connected or disconnected
+                    if (!mStateMachinesThread.getThreadHandler()
+                            .hasCallbacks(mDialingOutTimeoutEvent)) {
+                        mDialingOutTimeoutEvent = null;
+                    }
+                }
+            }
+        }
+        mStateMachinesThread.getThreadHandler().post(() -> {
+            boolean isCallIdleBefore = mSystemInterface.isCallIdle();
+            mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
+            mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
+            mSystemInterface.getHeadsetPhoneState().setCallState(callState);
+            // Suspend A2DP when call about is about to become active
+            if (callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED
+                    && !mSystemInterface.isCallIdle() && isCallIdleBefore) {
+                mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+            }
+        });
+        doForEachConnectedStateMachine(
+                stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
+                        new HeadsetCallState(numActive, numHeld, callState, number, type)));
+        mStateMachinesThread.getThreadHandler().post(() -> {
+            if (callState == HeadsetHalConstants.CALL_STATE_IDLE
+                    && mSystemInterface.isCallIdle() && !isAudioOn()) {
+                // Resume A2DP when call ended and SCO is not connected
+                mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
+            }
+        });
+
+    }
+
+    private void clccResponse(int index, int direction, int status, int mode, boolean mpty,
+            String number, int type) {
+        enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
+        doForEachConnectedStateMachine(
+                stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE,
+                        new HeadsetClccResponse(index, direction, status, mode, mpty, number,
+                                type)));
+    }
+
+    private boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
+            String arg) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        synchronized (mStateMachines) {
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                Log.w(TAG, "sendVendorSpecificResultCode: device " + device
+                        + " was never connected/connecting");
+                return false;
+            }
+            int connectionState = stateMachine.getConnectionState();
+            if (connectionState != BluetoothProfile.STATE_CONNECTED) {
+                return false;
+            }
+            // Currently we support only "+ANDROID".
+            if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) {
+                Log.w(TAG, "Disallowed unsolicited result code command: " + command);
+                return false;
+            }
+            stateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE,
+                    new HeadsetVendorSpecificResultCode(device, command, arg));
+        }
+        return true;
+    }
+
+    boolean isInbandRingingEnabled() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        return BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean(
+                DISABLE_INBAND_RINGING_PROPERTY, false) && !mInbandRingingRuntimeDisable;
+    }
+
+    /**
+     * Called from {@link HeadsetStateMachine} in state machine thread when there is a connection
+     * state change
+     *
+     * @param device remote device
+     * @param fromState from which connection state is the change
+     * @param toState to which connection state is the change
+     */
+    @VisibleForTesting
+    public void onConnectionStateChangedFromStateMachine(BluetoothDevice device, int fromState,
+            int toState) {
+        synchronized (mStateMachines) {
+            List<BluetoothDevice> audioConnectableDevices =
+                    getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+            if (fromState != BluetoothProfile.STATE_CONNECTED
+                    && toState == BluetoothProfile.STATE_CONNECTED) {
+                if (audioConnectableDevices.size() > 1) {
+                    mInbandRingingRuntimeDisable = true;
+                    doForEachConnectedStateMachine(
+                            stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
+                                    0));
+                }
+                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEADSET);
+            }
+            if (fromState != BluetoothProfile.STATE_DISCONNECTED
+                    && toState == BluetoothProfile.STATE_DISCONNECTED) {
+                if (audioConnectableDevices.size() <= 1) {
+                    mInbandRingingRuntimeDisable = false;
+                    doForEachConnectedStateMachine(
+                            stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
+                                    1));
+                }
+                if (device.equals(mActiveDevice)) {
+                    setActiveDevice(null);
+                }
+            }
+        }
+    }
+
+    /**
+     * Check if no audio mode is active
+     *
+     * @return false if virtual call, voice recognition, or Telecom call is active, true if all idle
+     */
+    private boolean isAudioModeIdle() {
+        synchronized (mStateMachines) {
+            if (mVoiceRecognitionStarted || mVirtualCallStarted || !mSystemInterface.isCallIdle()) {
+                Log.i(TAG, "isAudioModeIdle: not idle, mVoiceRecognitionStarted="
+                        + mVoiceRecognitionStarted + ", mVirtualCallStarted=" + mVirtualCallStarted
+                        + ", isCallIdle=" + mSystemInterface.isCallIdle());
+                return false;
+            }
+            return true;
+        }
+    }
+
+    private boolean shouldCallAudioBeActive() {
+        return mSystemInterface.isInCall() || (mSystemInterface.isRinging()
+                && isInbandRingingEnabled());
+    }
+
+    /**
+     * Only persist audio during active device switch when call audio is supposed to be active and
+     * virtual call has not been started. Virtual call is ignored because AudioService and
+     * applications should reconnect SCO during active device switch and forcing SCO connection
+     * here will make AudioService think SCO is started externally instead of by one of its SCO
+     * clients.
+     *
+     * @return true if call audio should be active and no virtual call is going on
+     */
+    private boolean shouldPersistAudio() {
+        return !mVirtualCallStarted && shouldCallAudioBeActive();
+    }
+
+    /**
+     * Called from {@link HeadsetStateMachine} in state machine thread when there is a audio
+     * connection state change
+     *
+     * @param device remote device
+     * @param fromState from which audio connection state is the change
+     * @param toState to which audio connection state is the change
+     */
+    @VisibleForTesting
+    public void onAudioStateChangedFromStateMachine(BluetoothDevice device, int fromState,
+            int toState) {
+        synchronized (mStateMachines) {
+            if (toState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                if (fromState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                    if (mActiveDevice != null && !mActiveDevice.equals(device)
+                            && shouldPersistAudio()) {
+                        if (!connectAudio(mActiveDevice)) {
+                            Log.w(TAG, "onAudioStateChangedFromStateMachine, failed to connect"
+                                    + " audio to new " + "active device " + mActiveDevice
+                                    + ", after " + device + " is disconnected from SCO");
+                        }
+                    }
+                }
+                if (mVoiceRecognitionStarted) {
+                    if (!stopVoiceRecognitionByHeadset(device)) {
+                        Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop voice "
+                                + "recognition");
+                    }
+                }
+                if (mVirtualCallStarted) {
+                    if (!stopScoUsingVirtualVoiceCall()) {
+                        Log.w(TAG, "onAudioStateChangedFromStateMachine: failed to stop virtual "
+                                + "voice call");
+                    }
+                }
+                // Unsuspend A2DP when SCO connection is gone and call state is idle
+                if (mSystemInterface.isCallIdle()) {
+                    mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
+                }
+            }
+        }
+    }
+
+    private void broadcastActiveDevice(BluetoothDevice device) {
+        logD("broadcastActiveDevice: " + 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
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM);
+    }
+
+    /**
+     * Check whether it is OK to accept a headset connection from a remote device
+     *
+     * @param device remote device that initiates the connection
+     * @return true if the connection is acceptable
+     */
+    public boolean okToAcceptConnection(BluetoothDevice device) {
+        // Check if this is an incoming connection in Quiet mode.
+        if (mAdapterService.isQuietModeEnabled()) {
+            Log.w(TAG, "okToAcceptConnection: return false as quiet mode enabled");
+            return false;
+        }
+        // Check priority and accept or reject the connection.
+        int priority = getPriority(device);
+        int bondState = mAdapterService.getBondState(device);
+        // Allow this connection only if the device is bonded. Any attempt to connect while
+        // bonding would potentially lead to an unauthorized connection.
+        if (bondState != BluetoothDevice.BOND_BONDED) {
+            Log.w(TAG, "okToAcceptConnection: return false, bondState=" + bondState);
+            return false;
+        } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED
+                && priority != BluetoothProfile.PRIORITY_ON
+                && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+            // Otherwise, reject the connection if priority is not valid.
+            Log.w(TAG, "okToAcceptConnection: return false, priority=" + priority);
+            return false;
+        }
+        List<BluetoothDevice> connectingConnectedDevices =
+                getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
+        if (connectingConnectedDevices.size() >= mMaxHeadsetConnections) {
+            Log.w(TAG, "Maximum number of connections " + mMaxHeadsetConnections
+                    + " was reached, rejecting connection from " + device);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Checks if SCO should be connected at current system state
+     *
+     * @param device device for SCO to be connected
+     * @return true if SCO is allowed to be connected
+     */
+    public boolean isScoAcceptable(BluetoothDevice device) {
+        synchronized (mStateMachines) {
+            if (device == null || !device.equals(mActiveDevice)) {
+                Log.w(TAG, "isScoAcceptable: rejected SCO since " + device
+                        + " is not the current active device " + mActiveDevice);
+                return false;
+            }
+            if (mForceScoAudio) {
+                return true;
+            }
+            if (!mAudioRouteAllowed) {
+                Log.w(TAG, "isScoAcceptable: rejected SCO since audio route is not allowed");
+                return false;
+            }
+            if (mVoiceRecognitionStarted || mVirtualCallStarted) {
+                return true;
+            }
+            if (shouldCallAudioBeActive()) {
+                return true;
+            }
+            Log.w(TAG, "isScoAcceptable: rejected SCO, inCall=" + mSystemInterface.isInCall()
+                    + ", voiceRecognition=" + mVoiceRecognitionStarted + ", ringing="
+                    + mSystemInterface.isRinging() + ", inbandRinging=" + isInbandRingingEnabled()
+                    + ", isVirtualCallStarted=" + mVirtualCallStarted);
+            return false;
+        }
+    }
+
+    /**
+     * Remove state machine in {@link #mStateMachines} for a {@link BluetoothDevice}
+     *
+     * @param device device whose state machine is to be removed.
+     */
+    void removeStateMachine(BluetoothDevice device) {
+        synchronized (mStateMachines) {
+            HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                Log.w(TAG, "removeStateMachine(), " + device + " does not have a state machine");
+                return;
+            }
+            Log.i(TAG, "removeStateMachine(), removing state machine for device: " + device);
+            HeadsetObjectsFactory.getInstance().destroyStateMachine(stateMachine);
+            mStateMachines.remove(device);
+        }
+    }
+
+    private boolean isOnStateMachineThread() {
+        final Looper myLooper = Looper.myLooper();
+        return myLooper != null && (mStateMachinesThread != null) && (myLooper.getThread().getId()
+                == mStateMachinesThread.getId());
     }
 
     @Override
     public void dump(StringBuilder sb) {
-        super.dump(sb);
-        if (mStateMachine != null) {
-            mStateMachine.dump(sb);
+        synchronized (mStateMachines) {
+            super.dump(sb);
+            ProfileService.println(sb, "mMaxHeadsetConnections: " + mMaxHeadsetConnections);
+            ProfileService.println(sb, "DefaultMaxHeadsetConnections: "
+                    + mAdapterService.getMaxConnectedAudioDevices());
+            ProfileService.println(sb, "mActiveDevice: " + mActiveDevice);
+            ProfileService.println(sb, "isInbandRingingEnabled: " + isInbandRingingEnabled());
+            ProfileService.println(sb,
+                    "isInbandRingingSupported: " + BluetoothHeadset.isInbandRingingSupported(this));
+            ProfileService.println(sb,
+                    "mInbandRingingRuntimeDisable: " + mInbandRingingRuntimeDisable);
+            ProfileService.println(sb, "mAudioRouteAllowed: " + mAudioRouteAllowed);
+            ProfileService.println(sb, "mVoiceRecognitionStarted: " + mVoiceRecognitionStarted);
+            ProfileService.println(sb,
+                    "mVoiceRecognitionTimeoutEvent: " + mVoiceRecognitionTimeoutEvent);
+            ProfileService.println(sb, "mVirtualCallStarted: " + mVirtualCallStarted);
+            ProfileService.println(sb, "mDialingOutTimeoutEvent: " + mDialingOutTimeoutEvent);
+            ProfileService.println(sb, "mForceScoAudio: " + mForceScoAudio);
+            ProfileService.println(sb, "mCreated: " + mCreated);
+            ProfileService.println(sb, "mStarted: " + mStarted);
+            ProfileService.println(sb,
+                    "AudioManager.isBluetoothScoOn(): " + mSystemInterface.getAudioManager()
+                            .isBluetoothScoOn());
+            ProfileService.println(sb, "Telecom.isInCall(): " + mSystemInterface.isInCall());
+            ProfileService.println(sb, "Telecom.isRinging(): " + mSystemInterface.isRinging());
+            for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
+                ProfileService.println(sb,
+                        "==== StateMachine for " + stateMachine.getDevice() + " ====");
+                stateMachine.dump(sb);
+            }
+        }
+    }
+
+    private static void logD(String message) {
+        if (DBG) {
+            Log.d(TAG, message);
         }
     }
 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetStackEvent.java b/src/com/android/bluetooth/hfp/HeadsetStackEvent.java
new file mode 100644
index 0000000..200fb92
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetStackEvent.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2017 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.hfp;
+
+import android.bluetooth.BluetoothDevice;
+
+import java.util.Objects;
+
+/**
+ * Callback events from native layer
+ */
+public class HeadsetStackEvent extends HeadsetMessageObject {
+    public static final int EVENT_TYPE_NONE = 0;
+    public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+    public static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+    public static final int EVENT_TYPE_VR_STATE_CHANGED = 3;
+    public static final int EVENT_TYPE_ANSWER_CALL = 4;
+    public static final int EVENT_TYPE_HANGUP_CALL = 5;
+    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_AT_CHLD = 10;
+    public static final int EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST = 11;
+    public static final int EVENT_TYPE_AT_CIND = 12;
+    public static final int EVENT_TYPE_AT_COPS = 13;
+    public static final int EVENT_TYPE_AT_CLCC = 14;
+    public static final int EVENT_TYPE_UNKNOWN_AT = 15;
+    public static final int EVENT_TYPE_KEY_PRESSED = 16;
+    public static final int EVENT_TYPE_WBS = 17;
+    public static final int EVENT_TYPE_BIND = 18;
+    public static final int EVENT_TYPE_BIEV = 19;
+    public static final int EVENT_TYPE_BIA = 20;
+
+    public final int type;
+    public final int valueInt;
+    public final int valueInt2;
+    public final String valueString;
+    public final HeadsetMessageObject valueObject;
+    public final BluetoothDevice device;
+
+    /**
+     * Create a headset stack event
+     *
+     * @param type type of the event
+     * @param device device of interest
+     */
+    public HeadsetStackEvent(int type, BluetoothDevice device) {
+        this(type, 0, 0, null, null, device);
+    }
+
+    /**
+     * Create a headset stack event
+     *
+     * @param type type of the event
+     * @param valueInt an integer value in the event
+     * @param device device of interest
+     */
+    public HeadsetStackEvent(int type, int valueInt, BluetoothDevice device) {
+        this(type, valueInt, 0, null, null, device);
+    }
+
+    /**
+     * Create a headset stack event
+     *
+     * @param type type of the event
+     * @param valueInt an integer value in the event
+     * @param valueInt2 another integer value in the event
+     * @param device device of interest
+     */
+    public HeadsetStackEvent(int type, int valueInt, int valueInt2, BluetoothDevice device) {
+        this(type, valueInt, valueInt2, null, null, device);
+    }
+
+    /**
+     * Create a headset stack event
+     *
+     * @param type type of the event
+     * @param valueString an string value in the event
+     * @param device device of interest
+     */
+    public HeadsetStackEvent(int type, String valueString, BluetoothDevice device) {
+        this(type, 0, 0, valueString, null, device);
+    }
+
+    /**
+     * Create a headset stack event
+     *
+     * @param type type of the event
+     * @param valueObject an object value in the event
+     * @param device device of interest
+     */
+    public HeadsetStackEvent(int type, HeadsetMessageObject valueObject, BluetoothDevice device) {
+        this(type, 0, 0, null, valueObject, device);
+    }
+
+    /**
+     * Create a headset stack event
+     *
+     * @param type type of the event
+     * @param valueInt an integer value in the event
+     * @param valueInt2 another integer value in the event
+     * @param valueString a string value in the event
+     * @param valueObject an object value in the event
+     * @param device device of interest
+     */
+    public HeadsetStackEvent(int type, int valueInt, int valueInt2, String valueString,
+            HeadsetMessageObject valueObject, BluetoothDevice device) {
+        this.type = type;
+        this.valueInt = valueInt;
+        this.valueInt2 = valueInt2;
+        this.valueString = valueString;
+        this.valueObject = valueObject;
+        this.device = Objects.requireNonNull(device);
+    }
+
+    /**
+     * Get a string that represents this event
+     *
+     * @return String that represents this event
+     */
+    public String getTypeString() {
+        switch (type) {
+            case EVENT_TYPE_NONE:
+                return "EVENT_TYPE_NONE";
+            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                return "EVENT_TYPE_CONNECTION_STATE_CHANGED";
+            case EVENT_TYPE_AUDIO_STATE_CHANGED:
+                return "EVENT_TYPE_AUDIO_STATE_CHANGED";
+            case EVENT_TYPE_VR_STATE_CHANGED:
+                return "EVENT_TYPE_VR_STATE_CHANGED";
+            case EVENT_TYPE_ANSWER_CALL:
+                return "EVENT_TYPE_ANSWER_CALL";
+            case EVENT_TYPE_HANGUP_CALL:
+                return "EVENT_TYPE_HANGUP_CALL";
+            case EVENT_TYPE_VOLUME_CHANGED:
+                return "EVENT_TYPE_VOLUME_CHANGED";
+            case EVENT_TYPE_DIAL_CALL:
+                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_AT_CHLD:
+                return "EVENT_TYPE_AT_CHLD";
+            case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
+                return "EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST";
+            case EVENT_TYPE_AT_CIND:
+                return "EVENT_TYPE_AT_CIND";
+            case EVENT_TYPE_AT_COPS:
+                return "EVENT_TYPE_AT_COPS";
+            case EVENT_TYPE_AT_CLCC:
+                return "EVENT_TYPE_AT_CLCC";
+            case EVENT_TYPE_UNKNOWN_AT:
+                return "EVENT_TYPE_UNKNOWN_AT";
+            case EVENT_TYPE_KEY_PRESSED:
+                return "EVENT_TYPE_KEY_PRESSED";
+            case EVENT_TYPE_WBS:
+                return "EVENT_TYPE_WBS";
+            case EVENT_TYPE_BIND:
+                return "EVENT_TYPE_BIND";
+            case EVENT_TYPE_BIEV:
+                return "EVENT_TYPE_BIEV";
+            case EVENT_TYPE_BIA:
+                return "EVENT_TYPE_BIA";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    @Override
+    public void buildString(StringBuilder builder) {
+        if (builder == null) {
+            return;
+        }
+        builder.append(getTypeString())
+                .append("[")
+                .append(type)
+                .append("]")
+                .append(", valInt=")
+                .append(valueInt)
+                .append(", valInt2=")
+                .append(valueInt2)
+                .append(", valString=")
+                .append(valueString)
+                .append(", valObject=")
+                .append(valueObject)
+                .append(", device=")
+                .append(device);
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index a7fa02a..d25d9ed 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -14,71 +14,67 @@
  * limitations under the License.
  */
 
-/**
- * Bluetooth Handset StateMachine
- *                      (Disconnected)
- *                           |    ^
- *                   CONNECT |    | DISCONNECTED
- *                           V    |
- *                         (Pending)
- *                           |    ^
- *                 CONNECTED |    | CONNECT
- *                           V    |
- *                        (Connected)
- *                           |    ^
- *             CONNECT_AUDIO |    | DISCONNECT_AUDIO
- *                           V    |
- *                         (AudioOn)
- */
 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.BluetoothUuid;
-import android.bluetooth.IBluetoothHeadsetPhone;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.ActivityNotFoundException;
 import android.media.AudioManager;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.IDeviceIdleController;
+import android.os.Looper;
 import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.PowerManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.PowerManager.WakeLock;
+import android.support.annotation.VisibleForTesting;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.PhoneStateListener;
 import android.util.Log;
-import com.android.bluetooth.Utils;
+
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
-import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import android.os.SystemProperties;
+import java.util.Objects;
+import java.util.Scanner;
 
-final class HeadsetStateMachine extends StateMachine {
+/**
+ * A Bluetooth Handset StateMachine
+ *                        (Disconnected)
+ *                           |      ^
+ *                   CONNECT |      | DISCONNECTED
+ *                           V      |
+ *                  (Connecting)   (Disconnecting)
+ *                           |      ^
+ *                 CONNECTED |      | DISCONNECT
+ *                           V      |
+ *                          (Connected)
+ *                           |      ^
+ *             CONNECT_AUDIO |      | AUDIO_DISCONNECTED
+ *                           V      |
+ *             (AudioConnecting)   (AudioDiconnecting)
+ *                           |      ^
+ *           AUDIO_CONNECTED |      | DISCONNECT_AUDIO
+ *                           V      |
+ *                           (AudioOn)
+ */
+@VisibleForTesting
+public class HeadsetStateMachine extends StateMachine {
     private static final String TAG = "HeadsetStateMachine";
     private static final boolean DBG = false;
-    // For Debugging only
-    private static int sRefCount = 0;
 
     private static final String HEADSET_NAME = "bt_headset_name";
     private static final String HEADSET_NREC = "bt_headset_nrec";
     private static final String HEADSET_WBS = "bt_wbs";
+    private static final String HEADSET_AUDIO_FEATURE_ON = "on";
+    private static final String HEADSET_AUDIO_FEATURE_OFF = "off";
 
     static final int CONNECT = 1;
     static final int DISCONNECT = 2;
@@ -90,119 +86,63 @@
     // message.obj is an intent AudioManager.VOLUME_CHANGED_ACTION
     // EXTRA_VOLUME_STREAM_TYPE is STREAM_BLUETOOTH_SCO
     static final int INTENT_SCO_VOLUME_CHANGED = 7;
-    static final int SET_MIC_VOLUME = 8;
+    static final int INTENT_CONNECTION_ACCESS_REPLY = 8;
     static final int CALL_STATE_CHANGED = 9;
-    static final int INTENT_BATTERY_CHANGED = 10;
-    static final int DEVICE_STATE_CHANGED = 11;
-    static final int SEND_CCLC_RESPONSE = 12;
-    static final int SEND_VENDOR_SPECIFIC_RESULT_CODE = 13;
+    static final int DEVICE_STATE_CHANGED = 10;
+    static final int SEND_CCLC_RESPONSE = 11;
+    static final int SEND_VENDOR_SPECIFIC_RESULT_CODE = 12;
+    static final int SEND_BSIR = 13;
+    static final int DIALING_OUT_RESULT = 14;
+    static final int VOICE_RECOGNITION_RESULT = 15;
 
-    static final int VIRTUAL_CALL_START = 14;
-    static final int VIRTUAL_CALL_STOP = 15;
-
-    static final int ENABLE_WBS = 16;
-    static final int DISABLE_WBS = 17;
-
-    static final int BIND_RESPONSE = 18;
-
-    private static final int STACK_EVENT = 101;
-    private static final int DIALING_OUT_TIMEOUT = 102;
-    private static final int START_VR_TIMEOUT = 103;
+    static final int STACK_EVENT = 101;
     private static final int CLCC_RSP_TIMEOUT = 104;
 
     private static final int CONNECT_TIMEOUT = 201;
 
-    private static final int DIALING_OUT_TIMEOUT_VALUE = 10000;
-    private static final int START_VR_TIMEOUT_VALUE = 5000;
-    private static final int CLCC_RSP_TIMEOUT_VALUE = 5000;
-    private static final int CONNECT_TIMEOUT_MILLIS = 30000;
+    private static final int CLCC_RSP_TIMEOUT_MS = 5000;
+    // NOTE: the value is not "final" - it is modified in the unit tests
+    @VisibleForTesting static int sConnectTimeoutMs = 30000;
 
-    // Max number of HF connections at any time
-    private int max_hf_connections = 1;
+    private static final HeadsetAgIndicatorEnableState DEFAULT_AG_INDICATOR_ENABLE_STATE =
+            new HeadsetAgIndicatorEnableState(true, true, true, true);
 
-    private static final int NBS_CODEC = 1;
-    private static final int WBS_CODEC = 2;
+    private final BluetoothDevice mDevice;
+
+    // State machine states
+    private final Disconnected mDisconnected = new Disconnected();
+    private final Connecting mConnecting = new Connecting();
+    private final Disconnecting mDisconnecting = new Disconnecting();
+    private final Connected mConnected = new Connected();
+    private final AudioOn mAudioOn = new AudioOn();
+    private final AudioConnecting mAudioConnecting = new AudioConnecting();
+    private final AudioDisconnecting mAudioDisconnecting = new AudioDisconnecting();
+    private HeadsetStateBase mPrevState;
+
+    // Run time dependencies
+    private final HeadsetService mHeadsetService;
+    private final AdapterService mAdapterService;
+    private final HeadsetNativeInterface mNativeInterface;
+    private final HeadsetSystemInterface mSystemInterface;
+
+    // Runtime states
+    private int mSpeakerVolume;
+    private int mMicVolume;
+    private HeadsetAgIndicatorEnableState mAgIndicatorEnableState;
+    // The timestamp when the device entered connecting/connected state
+    private long mConnectingTimestampMs = Long.MIN_VALUE;
+    // Audio Parameters like NREC
+    private final HashMap<String, String> mAudioParams = new HashMap<>();
+    // AT Phone book keeps a group of states used by AT+CPBR commands
+    private final AtPhonebook mPhonebook;
+    // HSP specific
+    private boolean mNeedDialingOutReply;
 
     // Keys are AT commands, and values are the company IDs.
     private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
-    // Hash for storing the Audio Parameters like NREC for connected headsets
-    private HashMap<BluetoothDevice, HashMap> mHeadsetAudioParam =
-            new HashMap<BluetoothDevice, HashMap>();
-    // Hash for storing the Remotedevice BRSF
-    private HashMap<BluetoothDevice, Integer> mHeadsetBrsf =
-            new HashMap<BluetoothDevice, Integer>();
-
-    private static final ParcelUuid[] HEADSET_UUIDS = {
-            BluetoothUuid.HSP, BluetoothUuid.Handsfree,
-    };
-
-    private Disconnected mDisconnected;
-    private Pending mPending;
-    private Connected mConnected;
-    private AudioOn mAudioOn;
-    // Multi HFP: add new class object
-    private MultiHFPending mMultiHFPending;
-
-    private HeadsetService mService;
-    private PowerManager mPowerManager;
-    private boolean mVirtualCallStarted = false;
-    private boolean mVoiceRecognitionStarted = false;
-    private boolean mWaitingForVoiceRecognition = false;
-    private WakeLock mStartVoiceRecognitionWakeLock; // held while waiting for voice recognition
-
-    private boolean mDialingOut = false;
-    private AudioManager mAudioManager;
-    private AtPhonebook mPhonebook;
-
-    private static Intent sVoiceCommandIntent;
-
-    private HeadsetPhoneState mPhoneState;
-    private int mAudioState;
-    private BluetoothAdapter mAdapter;
-    private IBluetoothHeadsetPhone mPhoneProxy;
-    private boolean mNativeAvailable;
-
-    // Indicates whether audio can be routed to the device.
-    private boolean mAudioRouteAllowed = true;
-
-    // Indicates whether SCO audio needs to be forced to open regardless ANY OTHER restrictions
-    private boolean mForceScoAudio = false;
-
-    // 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 mActiveScoDevice = null;
-    private BluetoothDevice mMultiDisconnectDevice = null;
-
-    // Multi HFP: Connected devices list holds all currently connected headsets
-    private ArrayList<BluetoothDevice> mConnectedDevicesList = new ArrayList<BluetoothDevice>();
 
     static {
-        classInitNative();
-
-        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<String, Integer>();
+        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<>();
         VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
                 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT,
                 BluetoothAssignedNumbers.PLANTRONICS);
@@ -217,234 +157,370 @@
                 BluetoothAssignedNumbers.APPLE);
     }
 
-    private HeadsetStateMachine(HeadsetService context) {
-        super(TAG);
-        mService = context;
-        mVoiceRecognitionStarted = false;
-        mWaitingForVoiceRecognition = false;
-
-        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mStartVoiceRecognitionWakeLock = mPowerManager.newWakeLock(
-                PowerManager.PARTIAL_WAKE_LOCK, TAG + ":VoiceRecognition");
-        mStartVoiceRecognitionWakeLock.setReferenceCounted(false);
-
-        mDialingOut = false;
-        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        mPhonebook = new AtPhonebook(mService, this);
-        mPhoneState = new HeadsetPhoneState(context, this);
-        mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());
-        intent.setComponent(intent.resolveSystemService(context.getPackageManager(), 0));
-        if (intent.getComponent() == null || !context.bindService(intent, mConnection, 0)) {
-            Log.e(TAG, "Could not bind to Bluetooth Headset Phone Service");
-        }
-
-        String max_hfp_clients = SystemProperties.get("bt.max.hfpclient.connections");
-        if (!max_hfp_clients.isEmpty() && (Integer.parseInt(max_hfp_clients) == 2)) {
-            max_hf_connections = Integer.parseInt(max_hfp_clients);
-        }
-        Log.d(TAG, "max_hf_connections = " + max_hf_connections);
-        Log.d(TAG,
-                "in-band_ringing_support = " + BluetoothHeadset.isInbandRingingSupported(mService));
-        initializeNative(max_hf_connections, BluetoothHeadset.isInbandRingingSupported(mService));
-        mNativeAvailable = true;
-
-        mDisconnected = new Disconnected();
-        mPending = new Pending();
-        mConnected = new Connected();
-        mAudioOn = new AudioOn();
-        // Multi HFP: initialise new class variable
-        mMultiHFPending = new MultiHFPending();
-
-        if (sVoiceCommandIntent == null) {
-            sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
-            sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        }
-
+    private HeadsetStateMachine(BluetoothDevice device, Looper looper,
+            HeadsetService headsetService, AdapterService adapterService,
+            HeadsetNativeInterface nativeInterface, HeadsetSystemInterface systemInterface) {
+        super(TAG, Objects.requireNonNull(looper, "looper cannot be null"));
+        // Enable/Disable StateMachine debug logs
+        setDbg(DBG);
+        mDevice = Objects.requireNonNull(device, "device cannot be null");
+        mHeadsetService = Objects.requireNonNull(headsetService, "headsetService cannot be null");
+        mNativeInterface =
+                Objects.requireNonNull(nativeInterface, "nativeInterface cannot be null");
+        mSystemInterface =
+                Objects.requireNonNull(systemInterface, "systemInterface cannot be null");
+        mAdapterService = Objects.requireNonNull(adapterService, "AdapterService cannot be null");
+        // Create phonebook helper
+        mPhonebook = new AtPhonebook(mHeadsetService, mNativeInterface);
+        // Initialize state machine
         addState(mDisconnected);
-        addState(mPending);
+        addState(mConnecting);
+        addState(mDisconnecting);
         addState(mConnected);
         addState(mAudioOn);
-        // Multi HFP: add State
-        addState(mMultiHFPending);
-
+        addState(mAudioConnecting);
+        addState(mAudioDisconnecting);
         setInitialState(mDisconnected);
     }
 
-    static HeadsetStateMachine make(HeadsetService context) {
-        Log.d(TAG, "make");
-        HeadsetStateMachine hssm = new HeadsetStateMachine(context);
-        hssm.start();
-        return hssm;
+    static HeadsetStateMachine make(BluetoothDevice device, Looper looper,
+            HeadsetService headsetService, AdapterService adapterService,
+            HeadsetNativeInterface nativeInterface, HeadsetSystemInterface systemInterface) {
+        HeadsetStateMachine stateMachine =
+                new HeadsetStateMachine(device, looper, headsetService, adapterService,
+                        nativeInterface, systemInterface);
+        stateMachine.start();
+        Log.i(TAG, "Created state machine " + stateMachine + " for " + device);
+        return stateMachine;
     }
 
-    public void doQuit() {
-        quitNow();
+    static void destroy(HeadsetStateMachine stateMachine) {
+        Log.i(TAG, "destroy");
+        if (stateMachine == null) {
+            Log.w(TAG, "destroy(), stateMachine is null");
+            return;
+        }
+        stateMachine.quitNow();
+        stateMachine.cleanup();
     }
 
     public void cleanup() {
-        if (mPhoneProxy != null) {
-            if (DBG) Log.d(TAG, "Unbinding service...");
-            synchronized (mConnection) {
-                try {
-                    mPhoneProxy = null;
-                    mService.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "Error unbinding from IBluetoothHeadsetPhone", re);
-                }
-            }
-        }
-        if (mPhoneState != null) {
-            mPhoneState.listenForPhoneState(false);
-            mPhoneState.cleanup();
-        }
         if (mPhonebook != null) {
             mPhonebook.cleanup();
         }
-        if (mHeadsetAudioParam != null) {
-            mHeadsetAudioParam.clear();
-        }
-        if (mHeadsetBrsf != null) {
-            mHeadsetBrsf.clear();
-        }
-        if (mConnectedDevicesList != null) {
-            mConnectedDevicesList.clear();
-        }
-        if (mNativeAvailable) {
-            cleanupNative();
-            mNativeAvailable = false;
-        }
+        mAudioParams.clear();
     }
 
     public void dump(StringBuilder sb) {
-        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
-        ProfileService.println(sb, "mTargetDevice: " + mTargetDevice);
-        ProfileService.println(sb, "mIncomingDevice: " + mIncomingDevice);
-        ProfileService.println(sb, "mActiveScoDevice: " + mActiveScoDevice);
-        ProfileService.println(sb, "mMultiDisconnectDevice: " + mMultiDisconnectDevice);
-        ProfileService.println(sb, "mVirtualCallStarted: " + mVirtualCallStarted);
-        ProfileService.println(sb, "mVoiceRecognitionStarted: " + mVoiceRecognitionStarted);
-        ProfileService.println(sb, "mWaitingForVoiceRecognition: " + mWaitingForVoiceRecognition);
-        ProfileService.println(sb, "StateMachine: " + this.toString());
-        ProfileService.println(sb, "mPhoneState: " + mPhoneState);
-        ProfileService.println(sb, "mAudioState: " + mAudioState);
+        ProfileService.println(sb, "  mCurrentDevice: " + mDevice);
+        ProfileService.println(sb, "  mCurrentState: " + getCurrentState());
+        ProfileService.println(sb, "  mPrevState: " + mPrevState);
+        ProfileService.println(sb, "  mConnectionState: " + getConnectionState());
+        ProfileService.println(sb, "  mAudioState: " + getAudioState());
+        ProfileService.println(sb, "  mNeedDialingOutReply: " + mNeedDialingOutReply);
+        ProfileService.println(sb, "  mSpeakerVolume: " + mSpeakerVolume);
+        ProfileService.println(sb, "  mMicVolume: " + mMicVolume);
+        ProfileService.println(sb,
+                "  mConnectingTimestampMs(uptimeMillis): " + mConnectingTimestampMs);
+        ProfileService.println(sb, "  StateMachine: " + this);
+        // Dump the state machine logs
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+        super.dump(new FileDescriptor(), printWriter, new String[]{});
+        printWriter.flush();
+        stringWriter.flush();
+        ProfileService.println(sb, "  StateMachineLog:");
+        Scanner scanner = new Scanner(stringWriter.toString());
+        while (scanner.hasNextLine()) {
+            String line = scanner.nextLine();
+            ProfileService.println(sb, "    " + line);
+        }
+        scanner.close();
     }
 
-    private class Disconnected extends State {
+    /**
+     * Base class for states used in this state machine to share common infrastructures
+     */
+    private abstract class HeadsetStateBase extends State {
         @Override
         public void enter() {
-            log("Enter Disconnected: " + getCurrentMessage().what + ", size: "
-                    + mConnectedDevicesList.size());
+            // Crash if mPrevState is null and state is not Disconnected
+            if (!(this instanceof Disconnected) && mPrevState == null) {
+                throw new IllegalStateException("mPrevState is null on enter()");
+            }
+            enforceValidConnectionStateTransition();
+        }
+
+        @Override
+        public void exit() {
+            mPrevState = this;
+        }
+
+        @Override
+        public String toString() {
+            return getName();
+        }
+
+        /**
+         * Broadcast audio and connection state changes to the system. This should be called at the
+         * end of enter() method after all the setup is done
+         */
+        void broadcastStateTransitions() {
+            if (mPrevState == null) {
+                return;
+            }
+            // TODO: Add STATE_AUDIO_DISCONNECTING constant to get rid of the 2nd part of this logic
+            if (getAudioStateInt() != mPrevState.getAudioStateInt() || (
+                    mPrevState instanceof AudioDisconnecting && this instanceof AudioOn)) {
+                stateLogD("audio state changed: " + mDevice + ": " + mPrevState + " -> " + this);
+                broadcastAudioState(mDevice, mPrevState.getAudioStateInt(), getAudioStateInt());
+            }
+            if (getConnectionStateInt() != mPrevState.getConnectionStateInt()) {
+                stateLogD(
+                        "connection state changed: " + mDevice + ": " + mPrevState + " -> " + this);
+                broadcastConnectionState(mDevice, mPrevState.getConnectionStateInt(),
+                        getConnectionStateInt());
+            }
+        }
+
+        // Should not be called from enter() method
+        void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) {
+            stateLogD("broadcastConnectionState " + device + ": " + fromState + "->" + toState);
+            mHeadsetService.onConnectionStateChangedFromStateMachine(device, fromState, toState);
+            Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
+            intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
+            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    HeadsetService.BLUETOOTH_PERM);
+        }
+
+        // Should not be called from enter() method
+        void broadcastAudioState(BluetoothDevice device, int fromState, int toState) {
+            stateLogD("broadcastAudioState: " + device + ": " + fromState + "->" + toState);
+            mHeadsetService.onAudioStateChangedFromStateMachine(device, fromState, toState);
+            Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
+            intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
+            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+            mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    HeadsetService.BLUETOOTH_PERM);
+        }
+
+        /**
+         * Verify if the current state transition is legal. This is supposed to be called from
+         * enter() method and crash if the state transition is out of the specification
+         *
+         * Note:
+         * This method uses state objects to verify transition because these objects should be final
+         * and any other instances are invalid
+         */
+        void enforceValidConnectionStateTransition() {
+            boolean result = false;
+            if (this == mDisconnected) {
+                result = mPrevState == null || mPrevState == mConnecting
+                        || mPrevState == mDisconnecting
+                        // TODO: edges to be removed after native stack refactoring
+                        // all transitions to disconnected state should go through a pending state
+                        // also, states should not go directly from an active audio state to
+                        // disconnected state
+                        || mPrevState == mConnected || mPrevState == mAudioOn
+                        || mPrevState == mAudioConnecting || mPrevState == mAudioDisconnecting;
+            } else if (this == mConnecting) {
+                result = mPrevState == mDisconnected;
+            } else if (this == mDisconnecting) {
+                result = mPrevState == mConnected
+                        // TODO: edges to be removed after native stack refactoring
+                        // all transitions to disconnecting state should go through connected state
+                        || mPrevState == mAudioConnecting || mPrevState == mAudioOn
+                        || mPrevState == mAudioDisconnecting;
+            } else if (this == mConnected) {
+                result = mPrevState == mConnecting || mPrevState == mAudioDisconnecting
+                        || mPrevState == mDisconnecting || mPrevState == mAudioConnecting
+                        // TODO: edges to be removed after native stack refactoring
+                        // all transitions to connected state should go through a pending state
+                        || mPrevState == mAudioOn || mPrevState == mDisconnected;
+            } else if (this == mAudioConnecting) {
+                result = mPrevState == mConnected;
+            } else if (this == mAudioDisconnecting) {
+                result = mPrevState == mAudioOn;
+            } else if (this == mAudioOn) {
+                result = mPrevState == mAudioConnecting || mPrevState == mAudioDisconnecting
+                        // TODO: edges to be removed after native stack refactoring
+                        // all transitions to audio connected state should go through a pending
+                        // state
+                        || mPrevState == mConnected;
+            }
+            if (!result) {
+                throw new IllegalStateException(
+                        "Invalid state transition from " + mPrevState + " to " + this
+                                + " for device " + mDevice);
+            }
+        }
+
+        void stateLogD(String msg) {
+            log(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
+        }
+
+        void stateLogW(String msg) {
+            logw(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
+        }
+
+        void stateLogE(String msg) {
+            loge(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
+        }
+
+        void stateLogV(String msg) {
+            logv(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
+        }
+
+        void stateLogI(String msg) {
+            logi(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
+        }
+
+        void stateLogWtfStack(String msg) {
+            Log.wtfStack(TAG, getName() + ": " + msg);
+        }
+
+        /**
+         * Process connection event
+         *
+         * @param message the current message for the event
+         * @param state connection state to transition to
+         */
+        public abstract void processConnectionEvent(Message message, int state);
+
+        /**
+         * Get a state value from {@link BluetoothProfile} that represents the connection state of
+         * this headset state
+         *
+         * @return a value in {@link BluetoothProfile#STATE_DISCONNECTED},
+         * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
+         * {@link BluetoothProfile#STATE_DISCONNECTING}
+         */
+        abstract int getConnectionStateInt();
+
+        /**
+         * Get an audio state value from {@link BluetoothHeadset}
+         * @return a value in {@link BluetoothHeadset#STATE_AUDIO_DISCONNECTED},
+         * {@link BluetoothHeadset#STATE_AUDIO_CONNECTING}, or
+         * {@link BluetoothHeadset#STATE_AUDIO_CONNECTED}
+         */
+        abstract int getAudioStateInt();
+
+    }
+
+    class Disconnected extends HeadsetStateBase {
+        @Override
+        int getConnectionStateInt() {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+
+        @Override
+        int getAudioStateInt() {
+            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            mConnectingTimestampMs = Long.MIN_VALUE;
             mPhonebook.resetAtState();
-            mPhoneState.listenForPhoneState(false);
-            mVoiceRecognitionStarted = false;
-            mWaitingForVoiceRecognition = false;
+            updateAgIndicatorEnableState(null);
+            mNeedDialingOutReply = false;
+            mAudioParams.clear();
+            broadcastStateTransitions();
+            // Remove the state machine for unbonded devices
+            if (mPrevState != null
+                    && mAdapterService.getBondState(mDevice) == BluetoothDevice.BOND_NONE) {
+                getHandler().post(() -> mHeadsetService.removeStateMachine(mDevice));
+            }
         }
 
         @Override
         public boolean processMessage(Message message) {
-            log("Disconnected process message: " + message.what + ", size: "
-                    + mConnectedDevicesList.size());
-            if (mConnectedDevicesList.size() != 0 || mTargetDevice != null
-                    || mIncomingDevice != null) {
-                Log.e(TAG, "ERROR: mConnectedDevicesList is not empty,"
-                                + "target, or mIncomingDevice not null in Disconnected");
-                return NOT_HANDLED;
-            }
-
             switch (message.what) {
                 case CONNECT:
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    Log.d(TAG, "Disconnected: connecting to device=" + device);
-                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                            BluetoothProfile.STATE_DISCONNECTED);
-                    if (!connectHfpNative(getByteAddress(device))) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
+                    stateLogD("Connecting to " + device);
+                    if (!mDevice.equals(device)) {
+                        stateLogE(
+                                "CONNECT failed, device=" + device + ", currentDevice=" + mDevice);
                         break;
                     }
-                    synchronized (HeadsetStateMachine.this) {
-                        mTargetDevice = device;
-                        transitionTo(mPending);
+                    if (!mNativeInterface.connectHfp(device)) {
+                        stateLogE("CONNECT failed for connectHfp(" + device + ")");
+                        // No state transition is involved, fire broadcast immediately
+                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+                                BluetoothProfile.STATE_DISCONNECTED);
+                        break;
                     }
-                    Message m = obtainMessage(CONNECT_TIMEOUT);
-                    m.obj = device;
-                    sendMessageDelayed(m, CONNECT_TIMEOUT_MILLIS);
+                    transitionTo(mConnecting);
                     break;
                 case DISCONNECT:
                     // ignore
                     break;
-                case INTENT_BATTERY_CHANGED:
-                    processIntentBatteryChanged((Intent) message.obj);
-                    break;
                 case CALL_STATE_CHANGED:
-                    processCallState((HeadsetCallState) message.obj, message.arg1 == 1);
+                    stateLogD("Ignoring CALL_STATE_CHANGED event");
                     break;
                 case DEVICE_STATE_CHANGED:
-                    log("Disconnected: ignoring DEVICE_STATE_CHANGED event");
+                    stateLogD("Ignoring DEVICE_STATE_CHANGED event");
                     break;
                 case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
-                    log("Disconnected: event type: " + event.type);
+                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
+                    stateLogD("STACK_EVENT: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        stateLogE("Event device does not match currentDevice[" + mDevice
+                                + "], event: " + event);
+                        break;
+                    }
                     switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(message, event.valueInt);
                             break;
                         default:
-                            Log.e(TAG, "Disconnected: unexpected stack event: " + event.type);
+                            stateLogE("Unexpected stack event: " + event);
                             break;
                     }
                     break;
                 default:
-                    Log.e(TAG, "Disconnected: unexpected message " + message.what);
+                    stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message);
                     return NOT_HANDLED;
             }
             return HANDLED;
         }
 
         @Override
-        public void exit() {
-            log("Exit Disconnected: " + getCurrentMessage().what);
-        }
-
-        // in Disconnected state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
-            Log.d(TAG,
-                    "Disconnected: processConnectionEvent, state=" + state + ", device=" + device);
+        public void processConnectionEvent(Message message, int state) {
+            stateLogD("processConnectionEvent, state=" + state);
             switch (state) {
                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
-                    Log.w(TAG, "Disconnected: ignore DISCONNECTED event, device=" + device);
+                    stateLogW("ignore DISCONNECTED event");
                     break;
-                // Both events result in Pending state as SLC establishment is still required
+                // Both events result in Connecting state as SLC establishment is still required
                 case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
                 case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
-                    if (okToConnect(device)) {
-                        Log.i(TAG,
-                                "Disconnected: connected/connecting incoming HF, device=" + device);
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        synchronized (HeadsetStateMachine.this) {
-                            mIncomingDevice = device;
-                            transitionTo(mPending);
-                        }
+                    if (mHeadsetService.okToAcceptConnection(mDevice)) {
+                        stateLogI("accept incoming connection");
+                        transitionTo(mConnecting);
                     } else {
-                        Log.i(TAG,
-                                "Disconnected: rejected incoming HF, priority="
-                                        + mService.getPriority(device) + " bondState="
-                                        + device.getBondState() + ", device=" + device);
-                        // reject the connection and stay in Disconnected state itself
-                        disconnectHfpNative(getByteAddress(device));
-                        // the other profile connection should be initiated
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
+                        stateLogI("rejected incoming HF, priority=" + mHeadsetService.getPriority(
+                                mDevice) + " bondState=" + mAdapterService.getBondState(mDevice));
+                        // Reject the connection and stay in Disconnected state itself
+                        if (!mNativeInterface.disconnectHfp(mDevice)) {
+                            stateLogE("failed to disconnect");
+                        }
+                        // Indicate rejection to other components.
+                        broadcastConnectionState(mDevice, BluetoothProfile.STATE_DISCONNECTED,
                                 BluetoothProfile.STATE_DISCONNECTED);
                     }
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
-                    Log.w(TAG, "Disconnected: ignore DISCONNECTING event, device=" + device);
+                    stateLogW("Ignore DISCONNECTING event");
                     break;
                 default:
-                    Log.e(TAG, "Disconnected: incorrect state: " + state);
+                    stateLogE("Incorrect state: " + state);
                     break;
             }
         }
@@ -453,1877 +529,902 @@
     // Per HFP 1.7.1 spec page 23/144, Pending state needs to handle
     //      AT+BRSF, AT+CIND, AT+CMER, AT+BIND, +CHLD
     // commands during SLC establishment
-    private class Pending extends State {
+    class Connecting extends HeadsetStateBase {
+        @Override
+        int getConnectionStateInt() {
+            return BluetoothProfile.STATE_CONNECTING;
+        }
+
+        @Override
+        int getAudioStateInt() {
+            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+        }
+
         @Override
         public void enter() {
-            log("Enter Pending: " + getCurrentMessage().what);
+            super.enter();
+            mConnectingTimestampMs = SystemClock.uptimeMillis();
+            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
+            broadcastStateTransitions();
         }
 
         @Override
         public boolean processMessage(Message message) {
-            log("Pending: processMessage=" + message.what
-                    + ", numConnectedDevices=" + mConnectedDevicesList.size());
             switch (message.what) {
                 case CONNECT:
                 case CONNECT_AUDIO:
+                case DISCONNECT:
                     deferMessage(message);
                     break;
-                case CONNECT_TIMEOUT:
-                    onConnectionStateChanged(HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED,
-                            getByteAddress(mTargetDevice));
-                    break;
-                case DISCONNECT: {
+                case CONNECT_TIMEOUT: {
+                    // We timed out trying to connect, transition to Disconnected state
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    Log.d(TAG, "Pending: DISCONNECT, device=" + device);
-                    if (mCurrentDevice != null && mTargetDevice != null
-                            && mTargetDevice.equals(device)) {
-                        // cancel connection to the mTargetDevice
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = null;
-                        }
-                    } else {
-                        deferMessage(message);
+                    if (!mDevice.equals(device)) {
+                        stateLogE("Unknown device timeout " + device);
+                        break;
                     }
+                    stateLogW("CONNECT_TIMEOUT");
+                    transitionTo(mDisconnected);
                     break;
                 }
-                case INTENT_BATTERY_CHANGED:
-                    processIntentBatteryChanged((Intent) message.obj);
-                    break;
                 case CALL_STATE_CHANGED:
-                    processCallState((HeadsetCallState) message.obj, message.arg1 == 1);
+                    stateLogD("ignoring CALL_STATE_CHANGED event");
                     break;
-                case BIND_RESPONSE: {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    bindResponseNative(message.arg1, message.arg2 == 1, getByteAddress(device));
-                    break;
-                }
                 case DEVICE_STATE_CHANGED:
-                    log("Pending: ignoring DEVICE_STATE_CHANGED event");
+                    stateLogD("ignoring DEVICE_STATE_CHANGED event");
                     break;
                 case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
-                    log("Pending: event type: " + event.type);
+                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
+                    stateLogD("STACK_EVENT: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        stateLogE("Event device does not match currentDevice[" + mDevice
+                                + "], event: " + event);
+                        break;
+                    }
                     switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(message, event.valueInt);
                             break;
-                        case EVENT_TYPE_AT_CHLD:
+                        case HeadsetStackEvent.EVENT_TYPE_AT_CHLD:
                             processAtChld(event.valueInt, event.device);
                             break;
-                        case EVENT_TYPE_AT_CIND:
+                        case HeadsetStackEvent.EVENT_TYPE_AT_CIND:
                             processAtCind(event.device);
                             break;
-                        case EVENT_TYPE_WBS:
-                            processWBSEvent(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_WBS:
+                            processWBSEvent(event.valueInt);
                             break;
-                        case EVENT_TYPE_BIND:
+                        case HeadsetStackEvent.EVENT_TYPE_BIND:
                             processAtBind(event.valueString, event.device);
                             break;
                         // Unexpected AT commands, we only handle them for comparability reasons
-                        case EVENT_TYPE_VR_STATE_CHANGED:
-                            Log.w(TAG,
-                                    "Pending: Unexpected VR event, device=" + event.device
-                                            + ", state=" + event.valueInt);
-                            processVrEvent(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED:
+                            stateLogW("Unexpected VR event, device=" + event.device + ", state="
+                                    + event.valueInt);
+                            processVrEvent(event.valueInt);
                             break;
-                        case EVENT_TYPE_DIAL_CALL:
-                            Log.w(TAG, "Pending: Unexpected dial event, device=" + event.device);
-                            processDialCall(event.valueString, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_DIAL_CALL:
+                            stateLogW("Unexpected dial event, device=" + event.device);
+                            processDialCall(event.valueString);
                             break;
-                        case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
-                            Log.w(TAG,
-                                    "Pending: Unexpected subscriber number event for" + event.device
-                                            + ", state=" + event.valueInt);
+                        case HeadsetStackEvent.EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
+                            stateLogW("Unexpected subscriber number event for" + event.device
+                                    + ", state=" + event.valueInt);
                             processSubscriberNumberRequest(event.device);
                             break;
-                        case EVENT_TYPE_AT_COPS:
-                            Log.w(TAG, "Pending: Unexpected COPS event for " + event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_AT_COPS:
+                            stateLogW("Unexpected COPS event for " + event.device);
                             processAtCops(event.device);
                             break;
-                        case EVENT_TYPE_AT_CLCC:
-                            Log.w(TAG, "Pending: Unexpected CLCC event for" + event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_AT_CLCC:
+                            Log.w(TAG, "Connecting: Unexpected CLCC event for" + event.device);
                             processAtClcc(event.device);
                             break;
-                        case EVENT_TYPE_UNKNOWN_AT:
-                            Log.w(TAG,
-                                    "Pending: Unexpected unknown AT event for" + event.device
-                                            + ", cmd=" + event.valueString);
+                        case HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT:
+                            stateLogW("Unexpected unknown AT event for" + event.device + ", cmd="
+                                    + event.valueString);
                             processUnknownAt(event.valueString, event.device);
                             break;
-                        case EVENT_TYPE_KEY_PRESSED:
-                            Log.w(TAG, "Pending: Unexpected key-press event for " + event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED:
+                            stateLogW("Unexpected key-press event for " + event.device);
                             processKeyPressed(event.device);
                             break;
-                        case EVENT_TYPE_BIEV:
-                            Log.w(TAG,
-                                    "Pending: Unexpected BIEV event for " + event.device
-                                            + ", indId=" + event.valueInt
-                                            + ", indVal=" + event.valueInt2);
+                        case HeadsetStackEvent.EVENT_TYPE_BIEV:
+                            stateLogW("Unexpected BIEV event for " + event.device + ", indId="
+                                    + event.valueInt + ", indVal=" + event.valueInt2);
                             processAtBiev(event.valueInt, event.valueInt2, event.device);
                             break;
-                        case EVENT_TYPE_VOLUME_CHANGED:
-                            Log.w(TAG, "Pending: Unexpected volume event for " + event.device);
-                            processVolumeEvent(event.valueInt, event.valueInt2, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_VOLUME_CHANGED:
+                            stateLogW("Unexpected volume event for " + event.device);
+                            processVolumeEvent(event.valueInt, event.valueInt2);
                             break;
-                        case EVENT_TYPE_ANSWER_CALL:
-                            Log.w(TAG, "Pending: Unexpected answer event for " + event.device);
-                            processAnswerCall(event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL:
+                            stateLogW("Unexpected answer event for " + event.device);
+                            mSystemInterface.answerCall(event.device);
                             break;
-                        case EVENT_TYPE_HANGUP_CALL:
-                            Log.w(TAG, "Pending: Unexpected hangup event for " + event.device);
-                            processHangupCall(event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_HANGUP_CALL:
+                            stateLogW("Unexpected hangup event for " + event.device);
+                            mSystemInterface.hangupCall(event.device);
                             break;
                         default:
-                            Log.e(TAG, "Pending: Unexpected event: " + event.type);
+                            stateLogE("Unexpected event: " + event);
                             break;
                     }
                     break;
                 default:
-                    Log.e(TAG, "Pending: unexpected message " + message.what);
+                    stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message);
                     return NOT_HANDLED;
             }
             return HANDLED;
         }
 
-        // in Pending state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
-            Log.d(TAG, "Pending: processConnectionEvent, state=" + state + ", device=" + device);
-            BluetoothDevice pendingDevice = getDeviceForMessage(CONNECT_TIMEOUT);
+        @Override
+        public void processConnectionEvent(Message message, int state) {
+            stateLogD("processConnectionEvent, state=" + state);
             switch (state) {
                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
-                    if (mConnectedDevicesList.contains(device)) {
-                        synchronized (HeadsetStateMachine.this) {
-                            mConnectedDevicesList.remove(device);
-                            mHeadsetAudioParam.remove(device);
-                            mHeadsetBrsf.remove(device);
-                            Log.d(TAG,
-                                    "Pending: device " + device.getAddress()
-                                            + " is removed in Pending state");
-                        }
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTING);
-                        synchronized (HeadsetStateMachine.this) {
-                            mCurrentDevice = null;
-                        }
-
-                        processWBSEvent(0, device); /* disable WBS audio parameters */
-
-                        if (mTargetDevice != null) {
-                            if (!connectHfpNative(getByteAddress(mTargetDevice))) {
-                                broadcastConnectionState(mTargetDevice,
-                                        BluetoothProfile.STATE_DISCONNECTED,
-                                        BluetoothProfile.STATE_CONNECTING);
-                                synchronized (HeadsetStateMachine.this) {
-                                    mTargetDevice = null;
-                                    transitionTo(mDisconnected);
-                                }
-                            }
-                        } else {
-                            synchronized (HeadsetStateMachine.this) {
-                                mIncomingDevice = null;
-                                if (mConnectedDevicesList.size() == 0) {
-                                    transitionTo(mDisconnected);
-                                } else {
-                                    processMultiHFConnected(device);
-                                }
-                            }
-                        }
-                    } else if (device.equals(mTargetDevice)) {
-                        // outgoing connection failed
-                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = null;
-                            if (mConnectedDevicesList.size() == 0) {
-                                transitionTo(mDisconnected);
-                            } else {
-                                transitionTo(mConnected);
-                            }
-                        }
-                    } else if (device.equals(mIncomingDevice)) {
-                        broadcastConnectionState(mIncomingDevice,
-                                BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        synchronized (HeadsetStateMachine.this) {
-                            mIncomingDevice = null;
-                            if (mConnectedDevicesList.size() == 0) {
-                                transitionTo(mDisconnected);
-                            } else {
-                                transitionTo(mConnected);
-                            }
-                        }
-                    } else {
-                        Log.e(TAG, "Pending: unknown device disconnected: " + device);
-                    }
+                    stateLogW("Disconnected");
+                    transitionTo(mDisconnected);
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
-                    if (mConnectedDevicesList.contains(device)) {
-                        // Disconnection failure does not go through SLC establishment
-                        Log.w(TAG, "Pending: disconnection failed for device " + device);
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTING);
-                        if (mTargetDevice != null) {
-                            broadcastConnectionState(mTargetDevice,
-                                    BluetoothProfile.STATE_DISCONNECTED,
-                                    BluetoothProfile.STATE_CONNECTING);
-                        }
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = null;
-                            transitionTo(mConnected);
-                        }
-                    } else if (!device.equals(mTargetDevice) && !device.equals(mIncomingDevice)) {
-                        Log.w(TAG,
-                                "Pending: unknown incoming HF connected on RFCOMM, device="
-                                        + device);
-                        if (!okToConnect(device)) {
-                            // reject the connection and stay in Pending state itself
-                            Log.i(TAG,
-                                    "Pending: unknown incoming HF rejected on RFCOMM, priority="
-                                            + mService.getPriority(device)
-                                            + " bondState=" + device.getBondState());
-                            disconnectHfpNative(getByteAddress(device));
-                        }
-                    } else {
-                        // Do nothing in normal case, wait for SLC connected event
-                        pendingDevice = null;
-                    }
+                    stateLogD("RFCOMM connected");
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
-                    int previousConnectionState = BluetoothProfile.STATE_CONNECTING;
-                    synchronized (HeadsetStateMachine.this) {
-                        mCurrentDevice = device;
-                        mConnectedDevicesList.add(device);
-                        if (device.equals(mTargetDevice)) {
-                            Log.d(TAG,
-                                    "Pending: added " + device
-                                            + " to mConnectedDevicesList, requested by us");
-                            mTargetDevice = null;
-                            transitionTo(mConnected);
-                        } else if (device.equals(mIncomingDevice)) {
-                            Log.d(TAG,
-                                    "Pending: added " + device
-                                            + " to mConnectedDevicesList, requested by remote");
-                            mIncomingDevice = null;
-                            transitionTo(mConnected);
-                        } else {
-                            Log.d(TAG,
-                                    "Pending: added " + device
-                                            + "to mConnectedDevicesList, unknown source");
-                            previousConnectionState = BluetoothProfile.STATE_DISCONNECTED;
-                        }
-                    }
-                    configAudioParameters(device);
-                    queryPhoneState();
-                    broadcastConnectionState(
-                            device, BluetoothProfile.STATE_CONNECTED, previousConnectionState);
+                    stateLogD("SLC connected");
+                    transitionTo(mConnected);
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
-                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                        log("current device tries to connect back");
-                        // TODO(BT) ignore or reject
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        // The stack is connecting to target device or
-                        // there is an incoming connection from the target device at the same time
-                        // we already broadcasted the intent, doing nothing here
-                        log("Stack and target device are connecting");
-                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                        Log.e(TAG, "Another connecting event on the incoming device");
-                    } else {
-                        // We get an incoming connecting request while Pending
-                        // TODO(BT) is stack handing this case? let's ignore it for now
-                        log("Incoming connection while pending, ignore");
-                    }
+                    // Ignored
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
-                    if ((mCurrentDevice != null) && mCurrentDevice.equals(device)) {
-                        // we already broadcasted the intent, doing nothing here
-                        log("stack is disconnecting mCurrentDevice");
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        Log.e(TAG, "TargetDevice is getting disconnected");
-                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                        Log.e(TAG, "IncomingDevice is getting disconnected");
-                    } else {
-                        Log.e(TAG, "Disconnecting unknow device: " + device);
-                    }
+                    stateLogW("Disconnecting");
                     break;
                 default:
-                    Log.e(TAG, "Incorrect state: " + state);
+                    stateLogE("Incorrect state " + state);
                     break;
             }
-            if (pendingDevice != null && pendingDevice.equals(device)) {
-                removeMessages(CONNECT_TIMEOUT);
-                Log.d(TAG, "Pending: removed CONNECT_TIMEOUT for device=" + pendingDevice);
-            }
         }
 
-        private void processMultiHFConnected(BluetoothDevice device) {
-            log("Pending state: processMultiHFConnected");
-            /* Assign the current activedevice again if the disconnected
-                         device equals to the current active device*/
-            if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
-                transitionTo(mConnected);
-                int deviceSize = mConnectedDevicesList.size();
-                mCurrentDevice = mConnectedDevicesList.get(deviceSize - 1);
-            } else {
-                // The disconnected device is not current active device
-                if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
-                    transitionTo(mAudioOn);
-                else
-                    transitionTo(mConnected);
-            }
-            log("processMultiHFConnected , the latest mCurrentDevice is:" + mCurrentDevice);
-            log("Pending state: processMultiHFConnected ,"
-                    + "fake broadcasting for mCurrentDevice");
-            broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
-                    BluetoothProfile.STATE_DISCONNECTED);
+        @Override
+        public void exit() {
+            removeMessages(CONNECT_TIMEOUT);
+            super.exit();
         }
     }
 
-    private class Connected extends State {
+    class Disconnecting extends HeadsetStateBase {
+        @Override
+        int getConnectionStateInt() {
+            return BluetoothProfile.STATE_DISCONNECTING;
+        }
+
+        @Override
+        int getAudioStateInt() {
+            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+        }
+
         @Override
         public void enter() {
-            // Remove pending connection attempts that were deferred during the pending
-            // state. This is to prevent auto connect attempts from disconnecting
-            // devices that previously successfully connected.
-            // TODO: This needs to check for multiple HFP connections, once supported...
-            removeDeferredMessages(CONNECT);
-
-            log("Enter Connected: " + getCurrentMessage().what + ", size: "
-                    + mConnectedDevicesList.size());
-            // start phone state listener here so that the CIND response as part of SLC can be
-            // responded to, correctly.
-            // we may enter Connected from Disconnected/Pending/AudioOn. listenForPhoneState
-            // internally handles multiple calls to start listen
-            mPhoneState.listenForPhoneState(true);
+            super.enter();
+            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
+            broadcastStateTransitions();
         }
 
         @Override
         public boolean processMessage(Message message) {
-            log("Connected process message=" + message.what
-                    + ", numConnectedDevices=" + mConnectedDevicesList.size());
             switch (message.what) {
-                case CONNECT: {
+                case CONNECT:
+                case CONNECT_AUDIO:
+                case DISCONNECT:
+                    deferMessage(message);
+                    break;
+                case CONNECT_TIMEOUT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    Log.d(TAG, "Connected: CONNECT, device=" + device);
-                    if (mConnectedDevicesList.contains(device)) {
-                        Log.w(TAG, "Connected: CONNECT, device " + device + " is connected");
+                    if (!mDevice.equals(device)) {
+                        stateLogE("Unknown device timeout " + device);
                         break;
                     }
-                    if (mConnectedDevicesList.size() >= max_hf_connections) {
-                        BluetoothDevice disconnectDevice = mConnectedDevicesList.get(0);
-                        Log.d(TAG, "Connected: Reach to max size, disconnect " + disconnectDevice);
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        if (disconnectHfpNative(getByteAddress(disconnectDevice))) {
-                            broadcastConnectionState(disconnectDevice,
-                                    BluetoothProfile.STATE_DISCONNECTING,
-                                    BluetoothProfile.STATE_CONNECTED);
-                        } else {
-                            Log.w(TAG, "Connected: failed to disconnect " + disconnectDevice);
-                            broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                    BluetoothProfile.STATE_CONNECTING);
-                            break;
-                        }
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = device;
-                            if (max_hf_connections == 1) {
-                                transitionTo(mPending);
-                            } else {
-                                mMultiDisconnectDevice = disconnectDevice;
-                                transitionTo(mMultiHFPending);
-                            }
-                        }
-                    } else if (mConnectedDevicesList.size() < max_hf_connections) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        if (!connectHfpNative(getByteAddress(device))) {
-                            broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                    BluetoothProfile.STATE_CONNECTING);
-                            break;
-                        }
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = device;
-                            // Transition to MultiHFPending state for Multi HF connection
-                            transitionTo(mMultiHFPending);
-                        }
+                    stateLogE("timeout");
+                    transitionTo(mDisconnected);
+                    break;
+                }
+                case STACK_EVENT:
+                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
+                    stateLogD("STACK_EVENT: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        stateLogE("Event device does not match currentDevice[" + mDevice
+                                + "], event: " + event);
+                        break;
                     }
-                    Message m = obtainMessage(CONNECT_TIMEOUT);
-                    m.obj = device;
-                    sendMessageDelayed(m, CONNECT_TIMEOUT_MILLIS);
-                } break;
-                case DISCONNECT: {
+                    switch (event.type) {
+                        case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(message, event.valueInt);
+                            break;
+                        default:
+                            stateLogE("Unexpected event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message);
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Disconnecting state
+        @Override
+        public void processConnectionEvent(Message message, int state) {
+            switch (state) {
+                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
+                    stateLogD("processConnectionEvent: Disconnected");
+                    transitionTo(mDisconnected);
+                    break;
+                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
+                    stateLogD("processConnectionEvent: Connected");
+                    transitionTo(mConnected);
+                    break;
+                default:
+                    stateLogE("processConnectionEvent: Bad state: " + state);
+                    break;
+            }
+        }
+
+        @Override
+        public void exit() {
+            removeMessages(CONNECT_TIMEOUT);
+            super.exit();
+        }
+    }
+
+    /**
+     * Base class for Connected, AudioConnecting, AudioOn, AudioDisconnecting states
+     */
+    private abstract class ConnectedBase extends HeadsetStateBase {
+        @Override
+        int getConnectionStateInt() {
+            return BluetoothProfile.STATE_CONNECTED;
+        }
+
+        /**
+         * Handle common messages in connected states. However, state specific messages must be
+         * handled individually.
+         *
+         * @param message Incoming message to handle
+         * @return True if handled successfully, False otherwise
+         */
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case CONNECT:
+                case DISCONNECT:
+                case CONNECT_AUDIO:
+                case DISCONNECT_AUDIO:
+                case CONNECT_TIMEOUT:
+                    throw new IllegalStateException(
+                            "Illegal message in generic handler: " + message);
+                case VOICE_RECOGNITION_START: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    Log.d(TAG, "Connected: DISCONNECT from device=" + device);
-                    if (!mConnectedDevicesList.contains(device)) {
-                        Log.w(TAG, "Connected: DISCONNECT, device " + device + " not connected");
+                    if (!mDevice.equals(device)) {
+                        stateLogW("VOICE_RECOGNITION_START failed " + device
+                                + " is not currentDevice");
                         break;
                     }
-                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
-                            BluetoothProfile.STATE_CONNECTED);
-                    if (!disconnectHfpNative(getByteAddress(device))) {
-                        // Failed disconnection request
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTING);
+                    if (!mNativeInterface.startVoiceRecognition(mDevice)) {
+                        stateLogW("Failed to start voice recognition");
                         break;
                     }
-                    // Pending disconnection confirmation
-                    if (mConnectedDevicesList.size() > 1) {
-                        mMultiDisconnectDevice = device;
-                        transitionTo(mMultiHFPending);
-                    } else {
-                        transitionTo(mPending);
-                    }
-                } break;
-                case CONNECT_AUDIO: {
-                    BluetoothDevice device = mCurrentDevice;
-                    Log.d(TAG, "Connected: CONNECT_AUDIO, device=" + device);
-                    if (!isScoAcceptable()) {
-                        Log.w(TAG,
-                                "No Active/Held call, no call setup, and no in-band ringing,"
-                                        + " not allowing SCO, device=" + device);
+                    break;
+                }
+                case VOICE_RECOGNITION_STOP: {
+                    BluetoothDevice device = (BluetoothDevice) message.obj;
+                    if (!mDevice.equals(device)) {
+                        stateLogW("VOICE_RECOGNITION_STOP failed " + device
+                                + " is not currentDevice");
                         break;
                     }
-                    // TODO(BT) when failure, broadcast audio connecting to disconnected intent
-                    //          check if device matches mCurrentDevice
-                    if (mActiveScoDevice != null) {
-                        Log.w(TAG, "Connected: CONNECT_AUDIO, mActiveScoDevice is not null");
-                        device = mActiveScoDevice;
+                    if (!mNativeInterface.stopVoiceRecognition(mDevice)) {
+                        stateLogW("Failed to stop voice recognition");
+                        break;
                     }
-                    connectAudioNative(getByteAddress(device));
-                } break;
-                case VOICE_RECOGNITION_START:
-                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
                     break;
-                case VOICE_RECOGNITION_STOP:
-                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
+                }
+                case CALL_STATE_CHANGED: {
+                    HeadsetCallState callState = (HeadsetCallState) message.obj;
+                    if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
+                        stateLogW("processCallState: failed to update call state " + callState);
+                        break;
+                    }
                     break;
-                case CALL_STATE_CHANGED:
-                    processCallState((HeadsetCallState) message.obj, message.arg1 == 1);
-                    break;
-                case INTENT_BATTERY_CHANGED:
-                    processIntentBatteryChanged((Intent) message.obj);
-                    break;
+                }
                 case DEVICE_STATE_CHANGED:
-                    processDeviceStateChanged((HeadsetDeviceState) message.obj);
+                    mNativeInterface.notifyDeviceStatus(mDevice, (HeadsetDeviceState) message.obj);
                     break;
                 case SEND_CCLC_RESPONSE:
                     processSendClccResponse((HeadsetClccResponse) message.obj);
                     break;
                 case CLCC_RSP_TIMEOUT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
-                } break;
+                    if (!mDevice.equals(device)) {
+                        stateLogW("CLCC_RSP_TIMEOUT failed " + device + " is not currentDevice");
+                        break;
+                    }
+                    mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0);
+                }
+                break;
                 case SEND_VENDOR_SPECIFIC_RESULT_CODE:
                     processSendVendorSpecificResultCode(
                             (HeadsetVendorSpecificResultCode) message.obj);
                     break;
-                case DIALING_OUT_TIMEOUT: {
+                case SEND_BSIR:
+                    mNativeInterface.sendBsir(mDevice, message.arg1 == 1);
+                    break;
+                case VOICE_RECOGNITION_RESULT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (mDialingOut) {
-                        mDialingOut = false;
-                        atResponseCodeNative(
-                                HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+                    if (!mDevice.equals(device)) {
+                        stateLogW("VOICE_RECOGNITION_RESULT failed " + device
+                                + " is not currentDevice");
+                        break;
                     }
-                } break;
-                case VIRTUAL_CALL_START:
-                    initiateScoUsingVirtualVoiceCall();
-                    break;
-                case VIRTUAL_CALL_STOP:
-                    terminateScoUsingVirtualVoiceCall();
-                    break;
-                case ENABLE_WBS: {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    configureWBSNative(getByteAddress(device), WBS_CODEC);
+                    mNativeInterface.atResponseCode(mDevice,
+                            message.arg1 == 1 ? HeadsetHalConstants.AT_RESPONSE_OK
+                                    : HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                     break;
                 }
-                case DISABLE_WBS: {
+                case DIALING_OUT_RESULT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    configureWBSNative(getByteAddress(device), NBS_CODEC);
-                    break;
-                }
-                case BIND_RESPONSE: {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    bindResponseNative(message.arg1, message.arg2 == 1, getByteAddress(device));
-                    break;
-                }
-                case START_VR_TIMEOUT: {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    if (mWaitingForVoiceRecognition) {
-                        device = (BluetoothDevice) message.obj;
-                        mWaitingForVoiceRecognition = false;
-                        Log.e(TAG, "Timeout waiting for voice recognition to start");
-                        atResponseCodeNative(
-                                HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+                    if (!mDevice.equals(device)) {
+                        stateLogW("DIALING_OUT_RESULT failed " + device + " is not currentDevice");
+                        break;
                     }
-                } break;
+                    if (mNeedDialingOutReply) {
+                        mNeedDialingOutReply = false;
+                        mNativeInterface.atResponseCode(mDevice,
+                                message.arg1 == 1 ? HeadsetHalConstants.AT_RESPONSE_OK
+                                        : HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+                    }
+                }
+                break;
+                case INTENT_CONNECTION_ACCESS_REPLY:
+                    handleAccessPermissionResult((Intent) message.obj);
+                    break;
                 case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
-                    log("Connected: event type: " + event.type + "event device : " + event.device);
+                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
+                    stateLogD("STACK_EVENT: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        stateLogE("Event device does not match currentDevice[" + mDevice
+                                + "], event: " + event);
+                        break;
+                    }
                     switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(message, event.valueInt);
                             break;
-                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
-                            processAudioEvent(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
+                            processAudioEvent(event.valueInt);
                             break;
-                        case EVENT_TYPE_VR_STATE_CHANGED:
-                            processVrEvent(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED:
+                            processVrEvent(event.valueInt);
                             break;
-                        case EVENT_TYPE_ANSWER_CALL:
-                            processAnswerCall(event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL:
+                            mSystemInterface.answerCall(event.device);
                             break;
-                        case EVENT_TYPE_HANGUP_CALL:
-                            processHangupCall(event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_HANGUP_CALL:
+                            mSystemInterface.hangupCall(event.device);
                             break;
-                        case EVENT_TYPE_VOLUME_CHANGED:
-                            processVolumeEvent(event.valueInt, event.valueInt2, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_VOLUME_CHANGED:
+                            processVolumeEvent(event.valueInt, event.valueInt2);
                             break;
-                        case EVENT_TYPE_DIAL_CALL:
-                            processDialCall(event.valueString, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_DIAL_CALL:
+                            processDialCall(event.valueString);
                             break;
-                        case EVENT_TYPE_SEND_DTMF:
-                            processSendDtmf(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_SEND_DTMF:
+                            mSystemInterface.sendDtmf(event.valueInt, event.device);
                             break;
-                        case EVENT_TYPE_NOICE_REDUCTION:
-                            processNoiceReductionEvent(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_NOICE_REDUCTION:
+                            processNoiseReductionEvent(event.valueInt == 1);
                             break;
-                        case EVENT_TYPE_WBS:
-                            processWBSEvent(event.valueInt, event.device);
+                        case HeadsetStackEvent.EVENT_TYPE_WBS:
+                            processWBSEvent(event.valueInt);
                             break;
-                        case EVENT_TYPE_AT_CHLD:
+                        case HeadsetStackEvent.EVENT_TYPE_AT_CHLD:
                             processAtChld(event.valueInt, event.device);
                             break;
-                        case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
+                        case HeadsetStackEvent.EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
                             processSubscriberNumberRequest(event.device);
                             break;
-                        case EVENT_TYPE_AT_CIND:
+                        case HeadsetStackEvent.EVENT_TYPE_AT_CIND:
                             processAtCind(event.device);
                             break;
-                        case EVENT_TYPE_AT_COPS:
+                        case HeadsetStackEvent.EVENT_TYPE_AT_COPS:
                             processAtCops(event.device);
                             break;
-                        case EVENT_TYPE_AT_CLCC:
+                        case HeadsetStackEvent.EVENT_TYPE_AT_CLCC:
                             processAtClcc(event.device);
                             break;
-                        case EVENT_TYPE_UNKNOWN_AT:
+                        case HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT:
                             processUnknownAt(event.valueString, event.device);
                             break;
-                        case EVENT_TYPE_KEY_PRESSED:
+                        case HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED:
                             processKeyPressed(event.device);
                             break;
-                        case EVENT_TYPE_BIND:
+                        case HeadsetStackEvent.EVENT_TYPE_BIND:
                             processAtBind(event.valueString, event.device);
                             break;
-                        case EVENT_TYPE_BIEV:
+                        case HeadsetStackEvent.EVENT_TYPE_BIEV:
                             processAtBiev(event.valueInt, event.valueInt2, event.device);
                             break;
+                        case HeadsetStackEvent.EVENT_TYPE_BIA:
+                            updateAgIndicatorEnableState(
+                                    (HeadsetAgIndicatorEnableState) event.valueObject);
+                            break;
                         default:
-                            Log.e(TAG, "Connected: Unknown stack event: " + event.type);
+                            stateLogE("Unknown stack event: " + event);
                             break;
                     }
                     break;
                 default:
-                    Log.e(TAG, "Connected: unexpected message " + message.what);
+                    stateLogE("Unexpected msg " + getMessageName(message.what) + ": " + message);
                     return NOT_HANDLED;
             }
             return HANDLED;
         }
 
-        // in Connected state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
-            Log.d(TAG, "Connected: processConnectionEvent, state=" + state + ", device=" + device);
+        @Override
+        public void processConnectionEvent(Message message, int state) {
+            stateLogD("processConnectionEvent, state=" + state);
             switch (state) {
-                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
-                    if (mConnectedDevicesList.contains(device)) {
-                        processWBSEvent(0, device); /* disable WBS audio parameters */
-                        synchronized (HeadsetStateMachine.this) {
-                            mConnectedDevicesList.remove(device);
-                            mHeadsetAudioParam.remove(device);
-                            mHeadsetBrsf.remove(device);
-                            Log.d(TAG, "device " + device.getAddress()
-                                            + " is removed in Connected state");
-
-                            if (mConnectedDevicesList.size() == 0) {
-                                mCurrentDevice = null;
-                                transitionTo(mDisconnected);
-                            } else {
-                                processMultiHFConnected(device);
-                            }
-                        }
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTED);
-                    } else {
-                        Log.e(TAG, "Disconnected from unknown device: " + device);
-                    }
+                case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
+                    stateLogE("processConnectionEvent: RFCOMM connected again, shouldn't happen");
                     break;
                 case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
-                    // Should have been rejected in CONNECTION_STATE_CONNECTED
-                    if (okToConnect(device)
-                            && (mConnectedDevicesList.size() < max_hf_connections)) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        synchronized (HeadsetStateMachine.this) {
-                            if (!mConnectedDevicesList.contains(device)) {
-                                mCurrentDevice = device;
-                                mConnectedDevicesList.add(device);
-                                Log.d(TAG,
-                                        "device " + device.getAddress()
-                                                + " is added in Connected state");
-                            }
-                            transitionTo(mConnected);
-                        }
-                        configAudioParameters(device);
-                    }
-                    queryPhoneState();
+                    stateLogE("processConnectionEvent: SLC connected again, shouldn't happen");
                     break;
-                case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
-                    if (mConnectedDevicesList.contains(device)) {
-                        mIncomingDevice = null;
-                        mTargetDevice = null;
-                        break;
-                    }
-                    Log.w(TAG, "HFP to be Connected in Connected state");
-                    if (!okToConnect(device)
-                            || (mConnectedDevicesList.size() >= max_hf_connections)) {
-                        // reject the connection and stay in Connected state itself
-                        Log.i(TAG, "Incoming Hf rejected. priority=" + mService.getPriority(device)
-                                        + " bondState=" + device.getBondState());
-                        disconnectHfpNative(getByteAddress(device));
-                    }
+                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
+                    stateLogI("processConnectionEvent: Disconnecting");
+                    transitionTo(mDisconnecting);
+                    break;
+                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
+                    stateLogI("processConnectionEvent: Disconnected");
+                    transitionTo(mDisconnected);
                     break;
                 default:
-                    Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
+                    stateLogE("processConnectionEvent: bad state: " + state);
                     break;
             }
         }
 
-        // in Connected state
-        private void processAudioEvent(int state, BluetoothDevice device) {
-            if (!mConnectedDevicesList.contains(device)) {
-                Log.e(TAG, "Audio changed on disconnected device: " + device);
-                return;
-            }
+        /**
+         * Each state should handle audio events differently
+         *
+         * @param state audio state
+         */
+        public abstract void processAudioEvent(int state);
+    }
 
-            switch (state) {
-                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
-                    if (!isScoAcceptable()) {
-                        Log.e(TAG, "Audio Connected without any listener");
-                        disconnectAudioNative(getByteAddress(device));
+    class Connected extends ConnectedBase {
+        @Override
+        int getAudioStateInt() {
+            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+        }
+
+        @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);
+                // Reset NREC on connect event. Headset will override later
+                processNoiseReductionEvent(true);
+                // Query phone state for initial setup
+                mSystemInterface.queryPhoneState();
+                // Remove pending connection attempts that were deferred during the pending
+                // state. This is to prevent auto connect attempts from disconnecting
+                // devices that previously successfully connected.
+                removeDeferredMessages(CONNECT);
+            }
+            broadcastStateTransitions();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case CONNECT: {
+                    BluetoothDevice device = (BluetoothDevice) message.obj;
+                    stateLogW("CONNECT, ignored, device=" + device + ", currentDevice" + mDevice);
+                    break;
+                }
+                case DISCONNECT: {
+                    BluetoothDevice device = (BluetoothDevice) message.obj;
+                    stateLogD("DISCONNECT from device=" + device);
+                    if (!mDevice.equals(device)) {
+                        stateLogW("DISCONNECT, device " + device + " not connected");
                         break;
                     }
+                    if (!mNativeInterface.disconnectHfp(device)) {
+                        // broadcast immediately as no state transition is involved
+                        stateLogE("DISCONNECT from " + device + " failed");
+                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
+                                BluetoothProfile.STATE_CONNECTED);
+                        break;
+                    }
+                    transitionTo(mDisconnecting);
+                }
+                break;
+                case CONNECT_AUDIO:
+                    stateLogD("CONNECT_AUDIO, device=" + mDevice);
+                    mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+                    if (!mNativeInterface.connectAudio(mDevice)) {
+                        mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
+                        stateLogE("Failed to connect SCO audio for " + mDevice);
+                        // No state change involved, fire broadcast immediately
+                        broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                        break;
+                    }
+                    transitionTo(mAudioConnecting);
+                    break;
+                case DISCONNECT_AUDIO:
+                    stateLogD("ignore DISCONNECT_AUDIO, device=" + mDevice);
+                    // ignore
+                    break;
+                default:
+                    return super.processMessage(message);
+            }
+            return HANDLED;
+        }
 
-                    // TODO(BT) should I save the state for next broadcast as the prevState?
-                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
-                    setAudioParameters(device); /*Set proper Audio Paramters.*/
-                    mAudioManager.setBluetoothScoOn(true);
-                    mActiveScoDevice = device;
-                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
-                            BluetoothHeadset.STATE_AUDIO_CONNECTING);
+        @Override
+        public void processAudioEvent(int state) {
+            stateLogD("processAudioEvent, state=" + state);
+            switch (state) {
+                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
+                    if (!mHeadsetService.isScoAcceptable(mDevice)) {
+                        stateLogW("processAudioEvent: reject incoming audio connection");
+                        if (!mNativeInterface.disconnectAudio(mDevice)) {
+                            stateLogE("processAudioEvent: failed to disconnect audio");
+                        }
+                        // Indicate rejection to other components.
+                        broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                        break;
+                    }
+                    stateLogI("processAudioEvent: audio connected");
                     transitionTo(mAudioOn);
                     break;
                 case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
-                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING;
-                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING,
-                            BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                    if (!mHeadsetService.isScoAcceptable(mDevice)) {
+                        stateLogW("processAudioEvent: reject incoming pending audio connection");
+                        if (!mNativeInterface.disconnectAudio(mDevice)) {
+                            stateLogE("processAudioEvent: failed to disconnect pending audio");
+                        }
+                        // Indicate rejection to other components.
+                        broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                        break;
+                    }
+                    stateLogI("processAudioEvent: audio connecting");
+                    transitionTo(mAudioConnecting);
                     break;
-                // TODO(BT) process other states
+                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
+                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
+                    // ignore
+                    break;
                 default:
-                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
+                    stateLogE("processAudioEvent: bad state: " + state);
                     break;
             }
         }
-
-        private void processMultiHFConnected(BluetoothDevice device) {
-            log("Connect state: processMultiHFConnected");
-            if (mActiveScoDevice != null && mActiveScoDevice.equals(device)) {
-                log("mActiveScoDevice is disconnected, setting it to null");
-                mActiveScoDevice = null;
-            }
-            /* Assign the current activedevice again if the disconnected
-                         device equals to the current active device */
-            if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
-                transitionTo(mConnected);
-                int deviceSize = mConnectedDevicesList.size();
-                mCurrentDevice = mConnectedDevicesList.get(deviceSize - 1);
-            } else {
-                // The disconnected device is not current active device
-                transitionTo(mConnected);
-            }
-            log("processMultiHFConnected , the latest mCurrentDevice is:" + mCurrentDevice);
-            log("Connect state: processMultiHFConnected ,"
-                    + "fake broadcasting for mCurrentDevice");
-            broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
-                    BluetoothProfile.STATE_DISCONNECTED);
-        }
     }
 
-    private class AudioOn extends State {
+    class AudioConnecting extends ConnectedBase {
+        @Override
+        int getAudioStateInt() {
+            return BluetoothHeadset.STATE_AUDIO_CONNECTING;
+        }
+
         @Override
         public void enter() {
-            log("Enter AudioOn: " + getCurrentMessage().what + ", size: "
-                    + mConnectedDevicesList.size());
+            super.enter();
+            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
+            broadcastStateTransitions();
         }
 
         @Override
         public boolean processMessage(Message message) {
-            log("AudioOn process message: " + message.what + ", size: "
-                    + mConnectedDevicesList.size());
             switch (message.what) {
-                case CONNECT: {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    Log.d(TAG, "AudioOn: CONNECT, device=" + device);
-                    if (mConnectedDevicesList.contains(device)) {
-                        Log.w(TAG, "AudioOn: CONNECT, device " + device + " is connected");
-                        break;
-                    }
-
-                    if (max_hf_connections == 1) {
-                        deferMessage(obtainMessage(DISCONNECT, mCurrentDevice));
-                        deferMessage(obtainMessage(CONNECT, device));
-                        if (disconnectAudioNative(getByteAddress(mCurrentDevice))) {
-                            Log.d(TAG, "AudioOn: disconnecting SCO, device=" + mCurrentDevice);
-                        } else {
-                            Log.e(TAG, "AudioOn: disconnect SCO failed, device=" + mCurrentDevice);
-                        }
-                        break;
-                    }
-
-                    if (mConnectedDevicesList.size() >= max_hf_connections) {
-                        BluetoothDevice disconnectDevice = mConnectedDevicesList.get(0);
-                        Log.d(TAG, "AudioOn: Reach to max size, disconnect " + disconnectDevice);
-
-                        if (mActiveScoDevice.equals(disconnectDevice)) {
-                            disconnectDevice = mConnectedDevicesList.get(1);
-                        }
-
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                BluetoothProfile.STATE_DISCONNECTED);
-
-                        if (disconnectHfpNative(getByteAddress(disconnectDevice))) {
-                            broadcastConnectionState(disconnectDevice,
-                                    BluetoothProfile.STATE_DISCONNECTING,
-                                    BluetoothProfile.STATE_CONNECTED);
-                        } else {
-                            Log.e(TAG, "AudioOn: Failed to disconnect " + disconnectDevice);
-                            broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                    BluetoothProfile.STATE_CONNECTING);
-                            break;
-                        }
-
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = device;
-                            mMultiDisconnectDevice = disconnectDevice;
-                            transitionTo(mMultiHFPending);
-                        }
-                    } else if (mConnectedDevicesList.size() < max_hf_connections) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        if (!connectHfpNative(getByteAddress(device))) {
-                            broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                    BluetoothProfile.STATE_CONNECTING);
-                            break;
-                        }
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = device;
-                            // Transtion to MultilHFPending state for Multi handsfree connection
-                            transitionTo(mMultiHFPending);
-                        }
-                    }
-                    Message m = obtainMessage(CONNECT_TIMEOUT);
-                    m.obj = device;
-                    sendMessageDelayed(m, CONNECT_TIMEOUT_MILLIS);
-                } break;
-                case CONNECT_TIMEOUT:
-                    onConnectionStateChanged(HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED,
-                            getByteAddress(mTargetDevice));
-                    break;
-                case DISCONNECT: {
-                    BluetoothDevice device = (BluetoothDevice) message.obj;
-                    Log.d(TAG, "AudioOn: DISCONNECT, device=" + device);
-                    if (!mConnectedDevicesList.contains(device)) {
-                        Log.w(TAG, "AudioOn: DISCONNECT, device " + device + " not connected");
-                        break;
-                    }
-                    if (mActiveScoDevice != null && mActiveScoDevice.equals(device)) {
-                        // The disconnected device is active SCO device
-                        Log.d(TAG, "AudioOn, DISCONNECT mActiveScoDevice=" + mActiveScoDevice);
-                        deferMessage(obtainMessage(DISCONNECT, message.obj));
-                        // Disconnect BT SCO first
-                        if (disconnectAudioNative(getByteAddress(mActiveScoDevice))) {
-                            log("Disconnecting SCO audio");
-                        } else {
-                            Log.w(TAG, "AudioOn, DISCONNECT failed, device=" + mActiveScoDevice);
-                            // if disconnect BT SCO failed, transition to mConnected state
-                            transitionTo(mConnected);
-                        }
-                    } else {
-                        /* Do not disconnect BT SCO if the disconnected
-                           device is not active SCO device */
-                        Log.d(TAG, "AudioOn, DISCONNECT, none active SCO device=" + device);
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING,
-                                BluetoothProfile.STATE_CONNECTED);
-                        // Should be still in AudioOn state
-                        if (!disconnectHfpNative(getByteAddress(device))) {
-                            Log.w(TAG, "AudioOn, DISCONNECT failed, device=" + device);
-                            broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                    BluetoothProfile.STATE_DISCONNECTING);
-                            break;
-                        }
-                        /* Transtion to MultiHFPending state for Multi handsfree connection */
-                        if (mConnectedDevicesList.size() > 1) {
-                            mMultiDisconnectDevice = device;
-                            transitionTo(mMultiHFPending);
-                        }
-                    }
-                } break;
+                case CONNECT:
+                case DISCONNECT:
+                case CONNECT_AUDIO:
                 case DISCONNECT_AUDIO:
-                    if (mActiveScoDevice != null) {
-                        if (disconnectAudioNative(getByteAddress(mActiveScoDevice))) {
-                            Log.d(TAG, "AudioOn: DISCONNECT_AUDIO, device=" + mActiveScoDevice);
-                        } else {
-                            Log.e(TAG,
-                                    "AudioOn: DISCONNECT_AUDIO failed, device=" + mActiveScoDevice);
-                        }
-                    } else {
-                        Log.w(TAG, "AudioOn: DISCONNECT_AUDIO, mActiveScoDevice is null");
-                    }
+                    deferMessage(message);
                     break;
-                case VOICE_RECOGNITION_START:
-                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
-                    break;
-                case VOICE_RECOGNITION_STOP:
-                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
-                    break;
-                case INTENT_SCO_VOLUME_CHANGED:
-                    if (mActiveScoDevice != null) {
-                        processIntentScoVolume((Intent) message.obj, mActiveScoDevice);
-                    }
-                    break;
-                case CALL_STATE_CHANGED:
-                    processCallState((HeadsetCallState) message.obj, message.arg1 == 1);
-                    break;
-                case INTENT_BATTERY_CHANGED:
-                    processIntentBatteryChanged((Intent) message.obj);
-                    break;
-                case DEVICE_STATE_CHANGED:
-                    processDeviceStateChanged((HeadsetDeviceState) message.obj);
-                    break;
-                case SEND_CCLC_RESPONSE:
-                    processSendClccResponse((HeadsetClccResponse) message.obj);
-                    break;
-                case CLCC_RSP_TIMEOUT: {
+                case CONNECT_TIMEOUT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
+                    if (!mDevice.equals(device)) {
+                        stateLogW("CONNECT_TIMEOUT for unknown device " + device);
+                        break;
+                    }
+                    stateLogW("CONNECT_TIMEOUT");
+                    transitionTo(mConnected);
                     break;
                 }
-                case SEND_VENDOR_SPECIFIC_RESULT_CODE:
-                    processSendVendorSpecificResultCode(
-                            (HeadsetVendorSpecificResultCode) message.obj);
-                    break;
-
-                case VIRTUAL_CALL_START:
-                    initiateScoUsingVirtualVoiceCall();
-                    break;
-                case VIRTUAL_CALL_STOP:
-                    terminateScoUsingVirtualVoiceCall();
-                    break;
-
-                case DIALING_OUT_TIMEOUT: {
-                    if (mDialingOut) {
-                        BluetoothDevice device = (BluetoothDevice) message.obj;
-                        mDialingOut = false;
-                        atResponseCodeNative(
-                                HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-                    }
-                    break;
-                }
-                case START_VR_TIMEOUT: {
-                    if (mWaitingForVoiceRecognition) {
-                        BluetoothDevice device = (BluetoothDevice) message.obj;
-                        mWaitingForVoiceRecognition = false;
-                        Log.e(TAG, "Timeout waiting for voice recognition"
-                                        + "to start");
-                        atResponseCodeNative(
-                                HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-                    }
-                    break;
-                }
-                case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
-                    log("AudioOn: event type: " + event.type);
-                    switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
-                            processAudioEvent(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_VR_STATE_CHANGED:
-                            processVrEvent(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_ANSWER_CALL:
-                            processAnswerCall(event.device);
-                            break;
-                        case EVENT_TYPE_HANGUP_CALL:
-                            processHangupCall(event.device);
-                            break;
-                        case EVENT_TYPE_VOLUME_CHANGED:
-                            processVolumeEvent(event.valueInt, event.valueInt2, event.device);
-                            break;
-                        case EVENT_TYPE_DIAL_CALL:
-                            processDialCall(event.valueString, event.device);
-                            break;
-                        case EVENT_TYPE_SEND_DTMF:
-                            processSendDtmf(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_NOICE_REDUCTION:
-                            processNoiceReductionEvent(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_AT_CHLD:
-                            processAtChld(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
-                            processSubscriberNumberRequest(event.device);
-                            break;
-                        case EVENT_TYPE_AT_CIND:
-                            processAtCind(event.device);
-                            break;
-                        case EVENT_TYPE_AT_COPS:
-                            processAtCops(event.device);
-                            break;
-                        case EVENT_TYPE_AT_CLCC:
-                            processAtClcc(event.device);
-                            break;
-                        case EVENT_TYPE_UNKNOWN_AT:
-                            processUnknownAt(event.valueString, event.device);
-                            break;
-                        case EVENT_TYPE_KEY_PRESSED:
-                            processKeyPressed(event.device);
-                            break;
-                        case EVENT_TYPE_BIND:
-                            processAtBind(event.valueString, event.device);
-                            break;
-                        case EVENT_TYPE_BIEV:
-                            processAtBiev(event.valueInt, event.valueInt2, event.device);
-                            break;
-                        default:
-                            Log.e(TAG, "AudioOn: Unknown stack event: " + event.type);
-                            break;
-                    }
-                    break;
                 default:
-                    Log.e(TAG, "AudioOn: unexpected message " + message.what);
-                    return NOT_HANDLED;
+                    return super.processMessage(message);
             }
             return HANDLED;
         }
 
-        // in AudioOn state. Some headsets disconnect RFCOMM prior to SCO down. Handle this
-        private void processConnectionEvent(int state, BluetoothDevice device) {
-            Log.d(TAG, "AudioOn: processConnectionEvent, state=" + state + ", device=" + device);
-            BluetoothDevice pendingDevice = getDeviceForMessage(CONNECT_TIMEOUT);
+        @Override
+        public void processAudioEvent(int state) {
             switch (state) {
-                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
-                    if (mConnectedDevicesList.contains(device)) {
-                        if (mActiveScoDevice != null && mActiveScoDevice.equals(device)
-                                && mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                            processAudioEvent(HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, device);
-                        }
-
-                        synchronized (HeadsetStateMachine.this) {
-                            mConnectedDevicesList.remove(device);
-                            mHeadsetAudioParam.remove(device);
-                            mHeadsetBrsf.remove(device);
-                            Log.d(TAG, "device " + device.getAddress()
-                                            + " is removed in AudioOn state");
-                            broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                    BluetoothProfile.STATE_CONNECTED);
-                            processWBSEvent(0, device); /* disable WBS audio parameters */
-                            if (mConnectedDevicesList.size() == 0) {
-                                transitionTo(mDisconnected);
-                            } else {
-                                processMultiHFConnected(device);
-                            }
-                        }
-                    } else {
-                        Log.e(TAG, "Disconnected from unknown device: " + device);
-                    }
+                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
+                    stateLogW("processAudioEvent: audio connection failed");
+                    transitionTo(mConnected);
                     break;
-                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
-                    // Should have been rejected in CONNECTION_STATE_CONNECTED
-                    if (okToConnect(device)
-                            && (mConnectedDevicesList.size() < max_hf_connections)) {
-                        Log.i(TAG, "AudioOn: accepted incoming HF");
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTED);
-                        synchronized (HeadsetStateMachine.this) {
-                            if (!mConnectedDevicesList.contains(device)) {
-                                mCurrentDevice = device;
-                                mConnectedDevicesList.add(device);
-                                Log.d(TAG,
-                                        "device " + device.getAddress()
-                                                + " is added in AudioOn state");
-                            }
-                        }
-                        configAudioParameters(device);
-                    }
-                    queryPhoneState();
+                case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
+                    // ignore, already in audio connecting state
                     break;
-                case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
-                    if (mConnectedDevicesList.contains(device)) {
-                        mIncomingDevice = null;
-                        mTargetDevice = null;
-                        break;
-                    }
-                    Log.w(TAG, "AudioOn: HFP to be connected device=" + device);
-                    if (!okToConnect(device)
-                            || (mConnectedDevicesList.size() >= max_hf_connections)) {
-                        // reject the connection and stay in Connected state itself
-                        Log.i(TAG,
-                                "AudioOn: rejected incoming HF, priority="
-                                        + mService.getPriority(device)
-                                        + " bondState=" + device.getBondState());
-                        disconnectHfpNative(getByteAddress(device));
-                    } else {
-                        // Do nothing in normal case, wait for SLC connected event
-                        pendingDevice = null;
-                    }
+                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
+                    // ignore, there is no BluetoothHeadset.STATE_AUDIO_DISCONNECTING
+                    break;
+                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
+                    stateLogI("processAudioEvent: audio connected");
+                    transitionTo(mAudioOn);
                     break;
                 default:
-                    Log.e(TAG, "Connection State Device: " + device + " bad state: " + state);
+                    stateLogE("processAudioEvent: bad state: " + state);
                     break;
             }
-            if (pendingDevice != null && pendingDevice.equals(device)) {
-                removeMessages(CONNECT_TIMEOUT);
-                Log.d(TAG, "AudioOn: removed CONNECT_TIMEOUT for device=" + pendingDevice);
-            }
         }
 
-        // in AudioOn state
-        private void processAudioEvent(int state, BluetoothDevice device) {
-            if (!mConnectedDevicesList.contains(device)) {
-                Log.e(TAG, "Audio changed on disconnected device: " + device);
-                return;
-            }
+        @Override
+        public void exit() {
+            removeMessages(CONNECT_TIMEOUT);
+            super.exit();
+        }
+    }
 
-            switch (state) {
-                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
-                    if (mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                        mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
-                        if (device.equals(mActiveScoDevice)) {
-                            mActiveScoDevice = null;
-                        }
-                        mAudioManager.setBluetoothScoOn(false);
-                        broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+    class AudioOn extends ConnectedBase {
+        @Override
+        int getAudioStateInt() {
+            return BluetoothHeadset.STATE_AUDIO_CONNECTED;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            removeDeferredMessages(CONNECT_AUDIO);
+            // Set active device to current active SCO device when the current active device
+            // is different from mCurrentDevice. This is to accommodate active device state
+            // mis-match between native and Java.
+            if (!mDevice.equals(mHeadsetService.getActiveDevice())
+                    && !hasDeferredMessages(DISCONNECT_AUDIO)) {
+                mHeadsetService.setActiveDevice(mDevice);
+            }
+            setAudioParameters();
+            broadcastStateTransitions();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case CONNECT: {
+                    BluetoothDevice device = (BluetoothDevice) message.obj;
+                    stateLogW("CONNECT, ignored, device=" + device + ", currentDevice" + mDevice);
+                    break;
+                }
+                case DISCONNECT: {
+                    BluetoothDevice device = (BluetoothDevice) message.obj;
+                    stateLogD("DISCONNECT, device=" + device);
+                    if (!mDevice.equals(device)) {
+                        stateLogW("DISCONNECT, device " + device + " not connected");
+                        break;
+                    }
+                    // Disconnect BT SCO first
+                    if (!mNativeInterface.disconnectAudio(mDevice)) {
+                        stateLogW("DISCONNECT failed, device=" + mDevice);
+                        // if disconnect BT SCO failed, transition to mConnected state to force
+                        // disconnect device
+                    }
+                    deferMessage(obtainMessage(DISCONNECT, mDevice));
+                    transitionTo(mAudioDisconnecting);
+                    break;
+                }
+                case CONNECT_AUDIO: {
+                    BluetoothDevice device = (BluetoothDevice) message.obj;
+                    if (!mDevice.equals(device)) {
+                        stateLogW("CONNECT_AUDIO device is not connected " + device);
+                        break;
+                    }
+                    stateLogW("CONNECT_AUDIO device auido is already connected " + device);
+                    break;
+                }
+                case DISCONNECT_AUDIO: {
+                    BluetoothDevice device = (BluetoothDevice) message.obj;
+                    if (!mDevice.equals(device)) {
+                        stateLogW("DISCONNECT_AUDIO, failed, device=" + device + ", currentDevice="
+                                + mDevice);
+                        break;
+                    }
+                    if (mNativeInterface.disconnectAudio(mDevice)) {
+                        stateLogD("DISCONNECT_AUDIO, device=" + mDevice);
+                        transitionTo(mAudioDisconnecting);
+                    } else {
+                        stateLogW("DISCONNECT_AUDIO failed, device=" + mDevice);
+                        broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                                 BluetoothHeadset.STATE_AUDIO_CONNECTED);
                     }
+                    break;
+                }
+                case INTENT_SCO_VOLUME_CHANGED:
+                    processIntentScoVolume((Intent) message.obj, mDevice);
+                    break;
+                case STACK_EVENT:
+                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
+                    stateLogD("STACK_EVENT: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        stateLogE("Event device does not match currentDevice[" + mDevice
+                                + "], event: " + event);
+                        break;
+                    }
+                    switch (event.type) {
+                        case HeadsetStackEvent.EVENT_TYPE_WBS:
+                            stateLogE("Cannot change WBS state when audio is connected: " + event);
+                            break;
+                        default:
+                            super.processMessage(message);
+                            break;
+                    }
+                    break;
+                default:
+                    return super.processMessage(message);
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public void processAudioEvent(int state) {
+            switch (state) {
+                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
+                    stateLogI("processAudioEvent: audio disconnected by remote");
                     transitionTo(mConnected);
                     break;
                 case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
-                    // TODO(BT) adding STATE_AUDIO_DISCONNECTING in BluetoothHeadset?
-                    // broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTING,
-                    //                    BluetoothHeadset.STATE_AUDIO_CONNECTED);
+                    stateLogI("processAudioEvent: audio being disconnected by remote");
+                    transitionTo(mAudioDisconnecting);
                     break;
                 default:
-                    Log.e(TAG, "Audio State Device: " + device + " bad state: " + state);
+                    stateLogE("processAudioEvent: bad state: " + state);
                     break;
             }
         }
 
         private void processIntentScoVolume(Intent intent, BluetoothDevice device) {
             int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
-            if (mPhoneState.getSpeakerVolume() != volumeValue) {
-                mPhoneState.setSpeakerVolume(volumeValue);
-                setVolumeNative(
-                        HeadsetHalConstants.VOLUME_TYPE_SPK, volumeValue, getByteAddress(device));
+            if (mSpeakerVolume != volumeValue) {
+                mSpeakerVolume = volumeValue;
+                mNativeInterface.setVolume(device, HeadsetHalConstants.VOLUME_TYPE_SPK,
+                        mSpeakerVolume);
             }
         }
-
-        private void processMultiHFConnected(BluetoothDevice device) {
-            log("AudioOn state: processMultiHFConnected");
-            /* Assign the current activedevice again if the disconnected
-                          device equals to the current active device */
-            if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
-                int deviceSize = mConnectedDevicesList.size();
-                mCurrentDevice = mConnectedDevicesList.get(deviceSize - 1);
-            }
-            if (mAudioState != BluetoothHeadset.STATE_AUDIO_CONNECTED) transitionTo(mConnected);
-
-            log("processMultiHFConnected , the latest mCurrentDevice is:" + mCurrentDevice);
-            log("AudioOn state: processMultiHFConnected ,"
-                    + "fake broadcasting for mCurrentDevice");
-            broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
-                    BluetoothProfile.STATE_DISCONNECTED);
-        }
     }
 
-    /* Add MultiHFPending state when atleast 1 HS is connected
-            and disconnect/connect new HS */
-    private class MultiHFPending extends State {
+    class AudioDisconnecting extends ConnectedBase {
+        @Override
+        int getAudioStateInt() {
+            // TODO: need BluetoothHeadset.STATE_AUDIO_DISCONNECTING
+            return BluetoothHeadset.STATE_AUDIO_CONNECTED;
+        }
+
         @Override
         public void enter() {
-            log("Enter MultiHFPending: " + getCurrentMessage().what + ", size: "
-                    + mConnectedDevicesList.size());
+            super.enter();
+            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs);
+            broadcastStateTransitions();
         }
 
         @Override
         public boolean processMessage(Message message) {
-            log("MultiHFPending process message: " + message.what + ", size: "
-                    + mConnectedDevicesList.size());
-
             switch (message.what) {
                 case CONNECT:
+                case DISCONNECT:
+                case CONNECT_AUDIO:
+                case DISCONNECT_AUDIO:
                     deferMessage(message);
                     break;
-
-                case CONNECT_AUDIO:
-                    if (mCurrentDevice != null) {
-                        connectAudioNative(getByteAddress(mCurrentDevice));
-                    }
-                    break;
-                case CONNECT_TIMEOUT:
-                    onConnectionStateChanged(HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED,
-                            getByteAddress(mTargetDevice));
-                    break;
-
-                case DISCONNECT_AUDIO:
-                    if (mActiveScoDevice != null) {
-                        if (disconnectAudioNative(getByteAddress(mActiveScoDevice))) {
-                            Log.d(TAG, "MultiHFPending, Disconnecting SCO audio for "
-                                            + mActiveScoDevice);
-                        } else {
-                            Log.e(TAG, "disconnectAudioNative failed"
-                                            + "for device = " + mActiveScoDevice);
-                        }
-                    }
-                    break;
-                case DISCONNECT:
+                case CONNECT_TIMEOUT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    Log.d(TAG, "MultiPending: DISCONNECT, device=" + device);
-                    if (mConnectedDevicesList.contains(device) && mTargetDevice != null
-                            && mTargetDevice.equals(device)) {
-                        // cancel connection to the mTargetDevice
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = null;
-                        }
-                    } else {
-                        deferMessage(message);
+                    if (!mDevice.equals(device)) {
+                        stateLogW("CONNECT_TIMEOUT for unknown device " + device);
+                        break;
                     }
+                    stateLogW("CONNECT_TIMEOUT");
+                    transitionTo(mConnected);
                     break;
-                case VOICE_RECOGNITION_START:
-                    device = (BluetoothDevice) message.obj;
-                    if (mConnectedDevicesList.contains(device)) {
-                        processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
-                    }
-                    break;
-                case VOICE_RECOGNITION_STOP:
-                    device = (BluetoothDevice) message.obj;
-                    if (mConnectedDevicesList.contains(device)) {
-                        processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
-                    }
-                    break;
-                case INTENT_SCO_VOLUME_CHANGED:
-                    if (mActiveScoDevice != null) {
-                        processIntentScoVolume((Intent) message.obj, mActiveScoDevice);
-                    }
-                    break;
-                case INTENT_BATTERY_CHANGED:
-                    processIntentBatteryChanged((Intent) message.obj);
-                    break;
-                case CALL_STATE_CHANGED:
-                    processCallState((HeadsetCallState) message.obj, message.arg1 == 1);
-                    break;
-                case DEVICE_STATE_CHANGED:
-                    processDeviceStateChanged((HeadsetDeviceState) message.obj);
-                    break;
-                case SEND_CCLC_RESPONSE:
-                    processSendClccResponse((HeadsetClccResponse) message.obj);
-                    break;
-                case CLCC_RSP_TIMEOUT: {
-                    device = (BluetoothDevice) message.obj;
-                    clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
-                } break;
-                case DIALING_OUT_TIMEOUT:
-                    if (mDialingOut) {
-                        device = (BluetoothDevice) message.obj;
-                        mDialingOut = false;
-                        atResponseCodeNative(
-                                HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-                    }
-                    break;
-                case VIRTUAL_CALL_START:
-                    device = (BluetoothDevice) message.obj;
-                    if (mConnectedDevicesList.contains(device)) {
-                        initiateScoUsingVirtualVoiceCall();
-                    }
-                    break;
-                case VIRTUAL_CALL_STOP:
-                    device = (BluetoothDevice) message.obj;
-                    if (mConnectedDevicesList.contains(device)) {
-                        terminateScoUsingVirtualVoiceCall();
-                    }
-                    break;
-                case START_VR_TIMEOUT:
-                    if (mWaitingForVoiceRecognition) {
-                        device = (BluetoothDevice) message.obj;
-                        mWaitingForVoiceRecognition = false;
-                        Log.e(TAG, "Timeout waiting for voice"
-                                        + "recognition to start");
-                        atResponseCodeNative(
-                                HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-                    }
-                    break;
-                case STACK_EVENT:
-                    StackEvent event = (StackEvent) message.obj;
-                    log("MultiHFPending: event type: " + event.type);
-                    switch (event.type) {
-                        case EVENT_TYPE_CONNECTION_STATE_CHANGED:
-                            processConnectionEvent(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_AUDIO_STATE_CHANGED:
-                            processAudioEvent(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_VR_STATE_CHANGED:
-                            processVrEvent(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_ANSWER_CALL:
-                            // TODO(BT) could answer call happen on Connected state?
-                            processAnswerCall(event.device);
-                            break;
-                        case EVENT_TYPE_HANGUP_CALL:
-                            // TODO(BT) could hangup call happen on Connected state?
-                            processHangupCall(event.device);
-                            break;
-                        case EVENT_TYPE_VOLUME_CHANGED:
-                            processVolumeEvent(event.valueInt, event.valueInt2, event.device);
-                            break;
-                        case EVENT_TYPE_DIAL_CALL:
-                            processDialCall(event.valueString, event.device);
-                            break;
-                        case EVENT_TYPE_SEND_DTMF:
-                            processSendDtmf(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_NOICE_REDUCTION:
-                            processNoiceReductionEvent(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST:
-                            processSubscriberNumberRequest(event.device);
-                            break;
-                        case EVENT_TYPE_AT_CIND:
-                            processAtCind(event.device);
-                            break;
-                        case EVENT_TYPE_AT_CHLD:
-                            processAtChld(event.valueInt, event.device);
-                            break;
-                        case EVENT_TYPE_AT_COPS:
-                            processAtCops(event.device);
-                            break;
-                        case EVENT_TYPE_AT_CLCC:
-                            processAtClcc(event.device);
-                            break;
-                        case EVENT_TYPE_UNKNOWN_AT:
-                            processUnknownAt(event.valueString, event.device);
-                            break;
-                        case EVENT_TYPE_KEY_PRESSED:
-                            processKeyPressed(event.device);
-                            break;
-                        case EVENT_TYPE_BIND:
-                            processAtBind(event.valueString, event.device);
-                            break;
-                        case EVENT_TYPE_BIEV:
-                            processAtBiev(event.valueInt, event.valueInt2, event.device);
-                            break;
-                        default:
-                            Log.e(TAG, "MultiHFPending: Unexpected event: " + event.type);
-                            break;
-                    }
-                    break;
+                }
                 default:
-                    Log.e(TAG, "MultiHFPending: unexpected message " + message.what);
-                    return NOT_HANDLED;
+                    return super.processMessage(message);
             }
             return HANDLED;
         }
 
-        // in MultiHFPending state
-        private void processConnectionEvent(int state, BluetoothDevice device) {
-            Log.d(TAG,
-                    "MultiPending: processConnectionEvent, state=" + state + ", device=" + device);
-            BluetoothDevice pendingDevice = getDeviceForMessage(CONNECT_TIMEOUT);
+        @Override
+        public void processAudioEvent(int state) {
             switch (state) {
-                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED:
-                    if (mConnectedDevicesList.contains(device)) {
-                        if (mMultiDisconnectDevice != null
-                                && mMultiDisconnectDevice.equals(device)) {
-                            mMultiDisconnectDevice = null;
-
-                            synchronized (HeadsetStateMachine.this) {
-                                mConnectedDevicesList.remove(device);
-                                mHeadsetAudioParam.remove(device);
-                                mHeadsetBrsf.remove(device);
-                                Log.d(TAG, "MultiHFPending: removed device=" + device);
-                                broadcastConnectionState(device,
-                                        BluetoothProfile.STATE_DISCONNECTED,
-                                        BluetoothProfile.STATE_DISCONNECTING);
-                            }
-
-                            if (mTargetDevice != null) {
-                                if (!connectHfpNative(getByteAddress(mTargetDevice))) {
-                                    broadcastConnectionState(mTargetDevice,
-                                            BluetoothProfile.STATE_DISCONNECTED,
-                                            BluetoothProfile.STATE_CONNECTING);
-                                    synchronized (HeadsetStateMachine.this) {
-                                        mTargetDevice = null;
-                                        if (mConnectedDevicesList.size() == 0) {
-                                            // Should be not in this state since it has at least
-                                            // one HF connected in MultiHFPending state
-                                            Log.w(TAG, "MultiHFPending: should not be here");
-                                            transitionTo(mDisconnected);
-                                        } else {
-                                            processMultiHFConnected(device);
-                                        }
-                                    }
-                                }
-                            } else {
-                                synchronized (HeadsetStateMachine.this) {
-                                    mIncomingDevice = null;
-                                    if (mConnectedDevicesList.size() == 0) {
-                                        transitionTo(mDisconnected);
-                                    } else {
-                                        processMultiHFConnected(device);
-                                    }
-                                }
-                            }
-                        } else {
-                            /* Another HF disconnected when one HF is connecting */
-                            synchronized (HeadsetStateMachine.this) {
-                                mConnectedDevicesList.remove(device);
-                                mHeadsetAudioParam.remove(device);
-                                mHeadsetBrsf.remove(device);
-                                Log.d(TAG, "device " + device.getAddress()
-                                                + " is removed in MultiHFPending state");
-                            }
-                            broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                    BluetoothProfile.STATE_CONNECTED);
-                        }
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = null;
-                            if (mConnectedDevicesList.size() == 0) {
-                                transitionTo(mDisconnected);
-                            } else {
-                                if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
-                                    transitionTo(mAudioOn);
-                                else
-                                    transitionTo(mConnected);
-                            }
-                        }
-                    } else {
-                        Log.e(TAG, "Unknown device Disconnected: " + device);
-                    }
+                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
+                    stateLogI("processAudioEvent: audio disconnected");
+                    transitionTo(mConnected);
                     break;
-                case HeadsetHalConstants.CONNECTION_STATE_CONNECTED:
-                    if (mConnectedDevicesList.contains(device)) {
-                        // Disconnection failure does not go through SLC establishment
-                        Log.w(TAG, "MultiPending: disconnection failed for device " + device);
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTING);
-                        if (mTargetDevice != null) {
-                            broadcastConnectionState(mTargetDevice,
-                                    BluetoothProfile.STATE_DISCONNECTED,
-                                    BluetoothProfile.STATE_CONNECTING);
-                        }
-                        synchronized (HeadsetStateMachine.this) {
-                            mTargetDevice = null;
-                            if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
-                                transitionTo(mAudioOn);
-                            else
-                                transitionTo(mConnected);
-                        }
-                    } else if (!device.equals(mTargetDevice)) {
-                        Log.w(TAG,
-                                "MultiPending: unknown incoming HF connected on RFCOMM"
-                                        + ", device=" + device);
-                        if (!okToConnect(device)
-                                || (mConnectedDevicesList.size() >= max_hf_connections)) {
-                            // reject the connection and stay in Pending state itself
-                            Log.i(TAG,
-                                    "MultiPending: unknown incoming HF rejected on RFCOMM"
-                                            + ", priority=" + mService.getPriority(device)
-                                            + ", bondState=" + device.getBondState());
-                            disconnectHfpNative(getByteAddress(device));
-                        } else {
-                            // Ok to connect, keep waiting for SLC connected event
-                            pendingDevice = null;
-                        }
-                    } else {
-                        // Do nothing in normal case, keep waiting for SLC connected event
-                        pendingDevice = null;
-                    }
+                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTING:
+                    // ignore
                     break;
-                case HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED:
-                    int previousConnectionState = BluetoothProfile.STATE_CONNECTING;
-                    synchronized (HeadsetStateMachine.this) {
-                        mCurrentDevice = device;
-                        mConnectedDevicesList.add(device);
-                        if (device.equals(mTargetDevice)) {
-                            Log.d(TAG,
-                                    "MultiPending: added " + device
-                                            + " to mConnectedDevicesList, requested by us");
-                            mTargetDevice = null;
-                            if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
-                                transitionTo(mAudioOn);
-                            else
-                                transitionTo(mConnected);
-                        } else {
-                            Log.d(TAG,
-                                    "MultiPending: added " + device
-                                            + "to mConnectedDevicesList, unknown source");
-                            previousConnectionState = BluetoothProfile.STATE_DISCONNECTED;
-                        }
-                    }
-                    configAudioParameters(device);
-                    queryPhoneState();
-                    broadcastConnectionState(
-                            device, BluetoothProfile.STATE_CONNECTED, previousConnectionState);
-                    break;
-                case HeadsetHalConstants.CONNECTION_STATE_CONNECTING:
-                    if (mConnectedDevicesList.contains(device)) {
-                        Log.e(TAG, "MultiPending: current device tries to connect back");
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        log("Stack and target device are connecting");
-                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                        Log.e(TAG, "MultiPending: Another connecting event on the incoming device");
-                    }
-                    break;
-                case HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING:
-                    if (mConnectedDevicesList.contains(device)) {
-                        log("stack is disconnecting mCurrentDevice");
-                    } else if (mTargetDevice != null && mTargetDevice.equals(device)) {
-                        Log.e(TAG, "MultiPending: TargetDevice is getting disconnected");
-                    } else if (mIncomingDevice != null && mIncomingDevice.equals(device)) {
-                        Log.e(TAG, "MultiPending: IncomingDevice is getting disconnected");
-                    } else {
-                        Log.e(TAG, "MultiPending: Disconnecting unknow device: " + device);
-                    }
-                    break;
-                default:
-                    Log.e(TAG, "MultiPending: Incorrect state: " + state);
-                    break;
-            }
-            if (pendingDevice != null && pendingDevice.equals(device)) {
-                removeMessages(CONNECT_TIMEOUT);
-                Log.d(TAG, "MultiPending: removed CONNECT_TIMEOUT for device=" + pendingDevice);
-            }
-        }
-
-        private void processAudioEvent(int state, BluetoothDevice device) {
-            if (!mConnectedDevicesList.contains(device)) {
-                Log.e(TAG, "MultiPending: Audio changed on disconnected device: " + device);
-                return;
-            }
-
-            switch (state) {
                 case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
-                    if (!isScoAcceptable()) {
-                        Log.e(TAG, "MultiPending: Audio Connected without any listener");
-                        disconnectAudioNative(getByteAddress(device));
-                        break;
-                    }
-                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
-                    setAudioParameters(device); /* Set proper Audio Parameters. */
-                    mAudioManager.setBluetoothScoOn(true);
-                    mActiveScoDevice = device;
-                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
-                            BluetoothHeadset.STATE_AUDIO_CONNECTING);
-                    /* The state should be still in MultiHFPending state when
-                       audio connected since other device is still connecting/
-                       disconnecting */
+                    stateLogW("processAudioEvent: audio disconnection failed");
+                    transitionTo(mAudioOn);
                     break;
                 case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
-                    mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING;
-                    broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING,
-                            BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                    // ignore, see if it goes into connected state, otherwise, timeout
                     break;
-                case HeadsetHalConstants.AUDIO_STATE_DISCONNECTED:
-                    if (mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                        mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
-                        if (device.equals(mActiveScoDevice)) {
-                            mActiveScoDevice = null;
-                        }
-                        mAudioManager.setBluetoothScoOn(false);
-                        broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
-                                BluetoothHeadset.STATE_AUDIO_CONNECTED);
-                    }
-                    /* The state should be still in MultiHFPending state when audio
-                       disconnected since other device is still connecting/
-                       disconnecting */
-                    break;
-
                 default:
-                    Log.e(TAG,
-                            "MultiPending: Audio State Device: " + device + " bad state: " + state);
+                    stateLogE("processAudioEvent: bad state: " + state);
                     break;
             }
         }
 
-        private void processMultiHFConnected(BluetoothDevice device) {
-            log("MultiHFPending: processMultiHFConnected, device=" + device);
-            if (mActiveScoDevice != null && mActiveScoDevice.equals(device)) {
-                log("mActiveScoDevice is disconnected, setting it to null");
-                mActiveScoDevice = null;
-            }
-            /* Assign the current activedevice again if the disconnected
-               device equals to the current active device */
-            if (mCurrentDevice != null && mCurrentDevice.equals(device)) {
-                int deviceSize = mConnectedDevicesList.size();
-                mCurrentDevice = mConnectedDevicesList.get(deviceSize - 1);
-            }
-            // The disconnected device is not current active device
-            if (mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED)
-                transitionTo(mAudioOn);
-            else
-                transitionTo(mConnected);
-            log("processMultiHFConnected , the latest mCurrentDevice is:" + mCurrentDevice);
-            log("MultiHFPending state: processMultiHFConnected ,"
-                    + "fake broadcasting for mCurrentDevice");
-            broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
-                    BluetoothProfile.STATE_DISCONNECTED);
-        }
-
-        private void processIntentScoVolume(Intent intent, BluetoothDevice device) {
-            int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
-            if (mPhoneState.getSpeakerVolume() != volumeValue) {
-                mPhoneState.setSpeakerVolume(volumeValue);
-                setVolumeNative(
-                        HeadsetHalConstants.VOLUME_TYPE_SPK, volumeValue, getByteAddress(device));
-            }
+        @Override
+        public void exit() {
+            removeMessages(CONNECT_TIMEOUT);
+            super.exit();
         }
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service);
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            mPhoneProxy = null;
-        }
-    };
-
-    // HFP Connection state of the device could be changed by the state machine
-    // in separate thread while this method is executing.
-    int getConnectionState(BluetoothDevice device) {
-        if (getCurrentState() == mDisconnected) {
-            if (DBG) Log.d(TAG, "currentState is Disconnected");
-            return BluetoothProfile.STATE_DISCONNECTED;
-        }
-
-        synchronized (this) {
-            IState currentState = getCurrentState();
-            if (DBG) Log.d(TAG, "currentState = " + currentState);
-            if (currentState == mPending) {
-                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
-                    return BluetoothProfile.STATE_CONNECTING;
-                }
-                if (mConnectedDevicesList.contains(device)) {
-                    return BluetoothProfile.STATE_DISCONNECTING;
-                }
-                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
-                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
-                }
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-
-            if (currentState == mMultiHFPending) {
-                if ((mTargetDevice != null) && mTargetDevice.equals(device)) {
-                    return BluetoothProfile.STATE_CONNECTING;
-                }
-                if ((mIncomingDevice != null) && mIncomingDevice.equals(device)) {
-                    return BluetoothProfile.STATE_CONNECTING; // incoming connection
-                }
-                if (mConnectedDevicesList.contains(device)) {
-                    if ((mMultiDisconnectDevice != null)
-                            && (!mMultiDisconnectDevice.equals(device))) {
-                        // The device is still connected
-                        return BluetoothProfile.STATE_CONNECTED;
-                    }
-                    return BluetoothProfile.STATE_DISCONNECTING;
-                }
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-
-            if (currentState == mConnected || currentState == mAudioOn) {
-                if (mConnectedDevicesList.contains(device)) {
-                    return BluetoothProfile.STATE_CONNECTED;
-                }
-                return BluetoothProfile.STATE_DISCONNECTED;
-            } else {
-                Log.e(TAG, "Bad currentState: " + currentState);
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-        }
+    /**
+     * Get the underlying device tracked by this state machine
+     *
+     * @return device in focus
+     */
+    @VisibleForTesting
+    public synchronized BluetoothDevice getDevice() {
+        return mDevice;
     }
 
-    List<BluetoothDevice> getConnectedDevices() {
-        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-        synchronized (this) {
-            devices.addAll(mConnectedDevicesList);
+    /**
+     * Get the current connection state of this state machine
+     *
+     * @return current connection state, one of {@link BluetoothProfile#STATE_DISCONNECTED},
+     * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
+     * {@link BluetoothProfile#STATE_DISCONNECTING}
+     */
+    @VisibleForTesting
+    public synchronized int getConnectionState() {
+        HeadsetStateBase state = (HeadsetStateBase) getCurrentState();
+        if (state == null) {
+            return BluetoothHeadset.STATE_DISCONNECTED;
         }
-        return devices;
+        return state.getConnectionStateInt();
     }
 
-    boolean isAudioOn() {
-        return (getCurrentState() == mAudioOn);
+    /**
+     * Get the current audio state of this state machine
+     *
+     * @return current audio state, one of {@link BluetoothHeadset#STATE_AUDIO_DISCONNECTED},
+     * {@link BluetoothHeadset#STATE_AUDIO_CONNECTING}, or
+     * {@link BluetoothHeadset#STATE_AUDIO_CONNECTED}
+     */
+    public synchronized int getAudioState() {
+        HeadsetStateBase state = (HeadsetStateBase) getCurrentState();
+        if (state == null) {
+            return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+        }
+        return state.getAudioStateInt();
     }
 
-    boolean isAudioConnected(BluetoothDevice device) {
-        synchronized (this) {
-            /*  Additional check for audio state included for the case when PhoneApp queries
-            Bluetooth Audio state, before we receive the close event from the stack for the
-            sco disconnect issued in AudioOn state. This was causing a mismatch in the
-            Incall screen UI. */
-
-            if (mActiveScoDevice != null && mActiveScoDevice.equals(device)
-                    && mAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void setAudioRouteAllowed(boolean allowed) {
-        mAudioRouteAllowed = allowed;
-        setScoAllowedNative(allowed);
-    }
-
-    public boolean getAudioRouteAllowed() {
-        return mAudioRouteAllowed;
-    }
-
-    public void setForceScoAudio(boolean forced) {
-        mForceScoAudio = forced;
-    }
-
-    int getAudioState(BluetoothDevice device) {
-        synchronized (this) {
-            if (mConnectedDevicesList.size() == 0) {
-                return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
-            }
-        }
-        return mAudioState;
-    }
-
-    private void processVrEvent(int state, BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processVrEvent device is null");
-            return;
-        }
-        Log.d(TAG, "processVrEvent: state=" + state + " mVoiceRecognitionStarted: "
-                        + mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: "
-                        + mWaitingForVoiceRecognition + " isInCall: " + isInCall());
-        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
-            if (!isVirtualCallInProgress() && !isInCall()) {
-                IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
-                        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-                if (dic != null) {
-                    try {
-                        dic.exitIdle("voice-command");
-                    } catch (RemoteException e) {
-                    }
-                }
-                try {
-                    mService.startActivity(sVoiceCommandIntent);
-                } catch (ActivityNotFoundException e) {
-                    atResponseCodeNative(
-                            HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-                    return;
-                }
-                expectVoiceRecognition(device);
-            } else {
-                // send error response if call is ongoing
-                atResponseCodeNative(
-                        HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-                return;
-            }
-        } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
-            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) {
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(device));
-                mVoiceRecognitionStarted = false;
-                mWaitingForVoiceRecognition = false;
-                if (!isInCall() && (mActiveScoDevice != null)) {
-                    disconnectAudioNative(getByteAddress(mActiveScoDevice));
-                    mAudioManager.setParameters("A2dpSuspended=false");
-                }
-            } else {
-                atResponseCodeNative(
-                        HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-            }
-        } else {
-            Log.e(TAG, "Bad Voice Recognition state: " + state);
-        }
-    }
-
-    private void processLocalVrEvent(int state) {
-        BluetoothDevice device = null;
-        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
-            boolean needAudio = true;
-            if (mVoiceRecognitionStarted || isInCall()) {
-                Log.e(TAG, "Voice recognition started when call is active. isInCall:" + isInCall()
-                                + " mVoiceRecognitionStarted: " + mVoiceRecognitionStarted);
-                return;
-            }
-            mVoiceRecognitionStarted = true;
-
-            if (mWaitingForVoiceRecognition) {
-                device = getDeviceForMessage(START_VR_TIMEOUT);
-                if (device == null) return;
-
-                Log.d(TAG, "Voice recognition started successfully");
-                mWaitingForVoiceRecognition = false;
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(device));
-                removeMessages(START_VR_TIMEOUT);
-            } else {
-                Log.d(TAG, "Voice recognition started locally");
-                needAudio = startVoiceRecognitionNative(getByteAddress(mCurrentDevice));
-                if (mCurrentDevice != null) device = mCurrentDevice;
-            }
-
-            if (needAudio && !isAudioOn()) {
-                Log.d(TAG, "Initiating audio connection for Voice Recognition");
-                // At this stage, we need to be sure that AVDTP is not streaming. This is needed
-                // to be compliant with the AV+HFP Whitepaper as we cannot have A2DP in
-                // streaming state while a SCO connection is established.
-                // This is needed for VoiceDial scenario alone and not for
-                // incoming call/outgoing call scenarios as the phone enters MODE_RINGTONE
-                // or MODE_IN_CALL which shall automatically suspend the AVDTP stream if needed.
-                // Whereas for VoiceDial we want to activate the SCO connection but we are still
-                // in MODE_NORMAL and hence the need to explicitly suspend the A2DP stream
-                mAudioManager.setParameters("A2dpSuspended=true");
-                if (device != null) {
-                    connectAudioNative(getByteAddress(device));
-                } else {
-                    Log.e(TAG, "device not found for VR");
-                }
-            }
-
-            if (mStartVoiceRecognitionWakeLock.isHeld()) {
-                mStartVoiceRecognitionWakeLock.release();
-            }
-        } else {
-            Log.d(TAG, "Voice Recognition stopped. mVoiceRecognitionStarted: "
-                            + mVoiceRecognitionStarted + " mWaitingForVoiceRecognition: "
-                            + mWaitingForVoiceRecognition);
-            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) {
-                mVoiceRecognitionStarted = false;
-                mWaitingForVoiceRecognition = false;
-
-                if (stopVoiceRecognitionNative(getByteAddress(mCurrentDevice)) && !isInCall()
-                        && mActiveScoDevice != null) {
-                    disconnectAudioNative(getByteAddress(mActiveScoDevice));
-                    mAudioManager.setParameters("A2dpSuspended=false");
-                }
-            }
-        }
-    }
-
-    private synchronized void expectVoiceRecognition(BluetoothDevice device) {
-        mWaitingForVoiceRecognition = true;
-        Message m = obtainMessage(START_VR_TIMEOUT);
-        m.obj = getMatchingDevice(device);
-        sendMessageDelayed(m, START_VR_TIMEOUT_VALUE);
-
-        if (!mStartVoiceRecognitionWakeLock.isHeld()) {
-            mStartVoiceRecognitionWakeLock.acquire(START_VR_TIMEOUT_VALUE);
-        }
-    }
-
-    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
-        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
-        int connectionState;
-        synchronized (this) {
-            for (BluetoothDevice device : bondedDevices) {
-                ParcelUuid[] featureUuids = device.getUuids();
-                if (!BluetoothUuid.containsAnyUuid(featureUuids, HEADSET_UUIDS)) {
-                    continue;
-                }
-                connectionState = getConnectionState(device);
-                for (int i = 0; i < states.length; i++) {
-                    if (connectionState == states[i]) {
-                        deviceList.add(device);
-                    }
-                }
-            }
-        }
-        return deviceList;
-    }
-
-    private BluetoothDevice getDeviceForMessage(int what) {
-        if (what == CONNECT_TIMEOUT) {
-            log("getDeviceForMessage: returning mTargetDevice for what=" + what);
-            return mTargetDevice;
-        }
-        if (mConnectedDevicesList.size() == 0) {
-            log("getDeviceForMessage: No connected device. what=" + what);
-            return null;
-        }
-        for (BluetoothDevice device : mConnectedDevicesList) {
-            if (getHandler().hasMessages(what, device)) {
-                log("getDeviceForMessage: returning " + device);
-                return device;
-            }
-        }
-        log("getDeviceForMessage: No matching device for " + what + ". Returning null");
-        return null;
-    }
-
-    private BluetoothDevice getMatchingDevice(BluetoothDevice device) {
-        for (BluetoothDevice matchingDevice : mConnectedDevicesList) {
-            if (matchingDevice.equals(device)) {
-                return matchingDevice;
-            }
-        }
-        return null;
-    }
-
-    // This method does not check for error conditon (newState == prevState)
-    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
-        log("Connection state " + device + ": " + prevState + "->" + newState);
-        if (prevState == BluetoothProfile.STATE_CONNECTED) {
-            // Headset is disconnecting, stop Virtual call if active.
-            terminateScoUsingVirtualVoiceCall();
-        }
-
-        Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        mService.sendBroadcastAsUser(intent, UserHandle.ALL,
-                HeadsetService.BLUETOOTH_PERM);
-    }
-
-    private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
-        if (prevState == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
-            // When SCO gets disconnected during call transfer, Virtual call
-            // needs to be cleaned up.So call terminateScoUsingVirtualVoiceCall.
-            terminateScoUsingVirtualVoiceCall();
-        }
-        Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        mService.sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM);
-        log("Audio state " + device + ": " + prevState + "->" + newState);
+    public long getConnectingTimestampMs() {
+        return mConnectingTimestampMs;
     }
 
     /*
@@ -2338,48 +1439,25 @@
         // assert: all elements of args are Serializable
         intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-
         intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "."
                 + Integer.toString(companyId));
-
-        mService.sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM);
+        mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM);
     }
 
-    private void configAudioParameters(BluetoothDevice device) {
-        // Reset NREC on connect event. Headset will override later
-        HashMap<String, Integer> AudioParamConfig = new HashMap<String, Integer>();
-        AudioParamConfig.put("NREC", 1);
-        mHeadsetAudioParam.put(device, AudioParamConfig);
-        mAudioManager.setParameters(
-                HEADSET_NAME + "=" + getCurrentDeviceName(device) + ";" + HEADSET_NREC + "=on");
-        Log.d(TAG, "configAudioParameters for device:" + device + " are: nrec = "
-                        + AudioParamConfig.get("NREC"));
-    }
-
-    private void setAudioParameters(BluetoothDevice device) {
-        // 1. update nrec value
-        // 2. update headset name
-        int mNrec = 0;
-        HashMap<String, Integer> AudioParam = mHeadsetAudioParam.get(device);
-        if (AudioParam != null && !AudioParam.isEmpty()) {
-            mNrec = AudioParam.get("NREC");
-        } else {
-            Log.e(TAG, "setAudioParameters: AudioParam not found");
-        }
-
-        if (mNrec == 1) {
-            Log.d(TAG, "Set NREC: 1 for device:" + device);
-            mAudioManager.setParameters(HEADSET_NREC + "=on");
-        } else {
-            Log.d(TAG, "Set NREC: 0 for device:" + device);
-            mAudioManager.setParameters(HEADSET_NREC + "=off");
-        }
-        mAudioManager.setParameters(HEADSET_NAME + "=" + getCurrentDeviceName(device));
+    private void setAudioParameters() {
+        String keyValuePairs = String.join(";", new String[]{
+                HEADSET_NAME + "=" + getCurrentDeviceName(),
+                HEADSET_NREC + "=" + mAudioParams.getOrDefault(HEADSET_NREC,
+                        HEADSET_AUDIO_FEATURE_OFF),
+                HEADSET_WBS + "=" + mAudioParams.getOrDefault(HEADSET_WBS,
+                        HEADSET_AUDIO_FEATURE_OFF)
+        });
+        Log.i(TAG, "setAudioParameters for " + mDevice + ": " + keyValuePairs);
+        mSystemInterface.getAudioManager().setParameters(keyValuePairs);
     }
 
     private String parseUnknownAt(String atString) {
         StringBuilder atCommand = new StringBuilder(atString.length());
-        String result = null;
 
         for (int i = 0; i < atString.length(); i++) {
             char c = atString.charAt(i);
@@ -2396,152 +1474,55 @@
                 atCommand.append(Character.toUpperCase(c));
             }
         }
-        result = atCommand.toString();
-        return result;
+        return atCommand.toString();
     }
 
     private int getAtCommandType(String atCommand) {
-        int commandType = mPhonebook.TYPE_UNKNOWN;
+        int commandType = AtPhonebook.TYPE_UNKNOWN;
         String atString = null;
         atCommand = atCommand.trim();
         if (atCommand.length() > 5) {
             atString = atCommand.substring(5);
-            if (atString.startsWith("?")) // Read
-                commandType = mPhonebook.TYPE_READ;
-            else if (atString.startsWith("=?")) // Test
-                commandType = mPhonebook.TYPE_TEST;
-            else if (atString.startsWith("=")) // Set
-                commandType = mPhonebook.TYPE_SET;
-            else
-                commandType = mPhonebook.TYPE_UNKNOWN;
+            if (atString.startsWith("?")) { // Read
+                commandType = AtPhonebook.TYPE_READ;
+            } else if (atString.startsWith("=?")) { // Test
+                commandType = AtPhonebook.TYPE_TEST;
+            } else if (atString.startsWith("=")) { // Set
+                commandType = AtPhonebook.TYPE_SET;
+            } else {
+                commandType = AtPhonebook.TYPE_UNKNOWN;
+            }
         }
         return commandType;
     }
 
-    /* Method to check if Virtual Call in Progress */
-    private boolean isVirtualCallInProgress() {
-        return mVirtualCallStarted;
-    }
-
-    void setVirtualCallInProgress(boolean state) {
-        mVirtualCallStarted = state;
-    }
-
-    /* NOTE: Currently the VirtualCall API does not support handling of
-    call transfers. If it is initiated from the handsfree device,
-    HeadsetStateMachine will end the virtual call by calling
-    terminateScoUsingVirtualVoiceCall() in broadcastAudioState() */
-    synchronized boolean initiateScoUsingVirtualVoiceCall() {
-        log("initiateScoUsingVirtualVoiceCall: Received");
-        // 1. Check if the SCO state is idle
-        if (isInCall() || mVoiceRecognitionStarted) {
-            Log.e(TAG, "initiateScoUsingVirtualVoiceCall: Call in progress.");
-            return false;
-        }
-
-        // 2. Send virtual phone state changed to initialize SCO
-        processCallState(
-                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0), true);
-        processCallState(
-                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0), true);
-        processCallState(
-                new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0), true);
-        setVirtualCallInProgress(true);
-        // Done
-        log("initiateScoUsingVirtualVoiceCall: Done");
-        return true;
-    }
-
-    synchronized boolean terminateScoUsingVirtualVoiceCall() {
-        log("terminateScoUsingVirtualVoiceCall: Received");
-
-        if (!isVirtualCallInProgress()) {
-            Log.w(TAG, "terminateScoUsingVirtualVoiceCall: No present call to terminate");
-            return false;
-        }
-
-        // 2. Send virtual phone state changed to close SCO
-        processCallState(
-                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0), true);
-        setVirtualCallInProgress(false);
-        // Done
-        log("terminateScoUsingVirtualVoiceCall: Done");
-        return true;
-    }
-
-    private void processAnswerCall(BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processAnswerCall device is null");
-            return;
-        }
-
-        if (mPhoneProxy != null) {
-            try {
-                mPhoneProxy.answerCall();
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        } else {
-            Log.e(TAG, "Handsfree phone proxy null for answering call");
-        }
-    }
-
-    private void processHangupCall(BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processHangupCall device is null");
-            return;
-        }
-        // Close the virtual call if active. Virtual call should be
-        // terminated for CHUP callback event
-        if (isVirtualCallInProgress()) {
-            terminateScoUsingVirtualVoiceCall();
-        } else {
-            if (mPhoneProxy != null) {
-                try {
-                    mPhoneProxy.hangupCall();
-                } catch (RemoteException e) {
-                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                }
-            } else {
-                Log.e(TAG, "Handsfree phone proxy null for hanging up call");
-            }
-        }
-    }
-
-    private void processDialCall(String number, BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processDialCall device is null");
-            return;
-        }
-
+    private void processDialCall(String number) {
         String dialNumber;
-        if (mDialingOut) {
-            log("processDialCall, already dialling");
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+        if (mHeadsetService.hasDeviceInitiatedDialingOut()) {
+            Log.w(TAG, "processDialCall, already dialling");
+            mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
             return;
         }
         if ((number == null) || (number.length() == 0)) {
             dialNumber = mPhonebook.getLastDialledNumber();
             if (dialNumber == null) {
-                log("processDialCall, last dial number null");
-                atResponseCodeNative(
-                        HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+                Log.w(TAG, "processDialCall, last dial number null");
+                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                 return;
             }
         } else if (number.charAt(0) == '>') {
             // Yuck - memory dialling requested.
             // Just dial last number for now
             if (number.startsWith(">9999")) { // for PTS test
-                atResponseCodeNative(
-                        HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+                Log.w(TAG, "Number is too big");
+                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                 return;
             }
             log("processDialCall, memory dial do last dial for now");
             dialNumber = mPhonebook.getLastDialledNumber();
             if (dialNumber == null) {
-                log("processDialCall, last dial number null");
-                atResponseCodeNative(
-                        HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+                Log.w(TAG, "processDialCall, last dial number null");
+                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                 return;
             }
         } else {
@@ -2549,301 +1530,147 @@
             if (number.charAt(number.length() - 1) == ';') {
                 number = number.substring(0, number.length() - 1);
             }
-
             dialNumber = PhoneNumberUtils.convertPreDial(number);
         }
-        // Check for virtual call to terminate before sending Call Intent
-        terminateScoUsingVirtualVoiceCall();
-
-        Intent intent = new Intent(
-                Intent.ACTION_CALL_PRIVILEGED, Uri.fromParts(SCHEME_TEL, dialNumber, null));
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mService.startActivity(intent);
-        // TODO(BT) continue send OK reults code after call starts
-        //          hold wait lock, start a timer, set wait call flag
-        //          Get call started indication from bluetooth phone
-        mDialingOut = true;
-        Message m = obtainMessage(DIALING_OUT_TIMEOUT);
-        m.obj = getMatchingDevice(device);
-        sendMessageDelayed(m, DIALING_OUT_TIMEOUT_VALUE);
-    }
-
-    private void processVolumeEvent(int volumeType, int volume, BluetoothDevice device) {
-        if (device != null && !device.equals(mActiveScoDevice) && mPhoneState.isInCall()) {
-            Log.w(TAG, "ignore processVolumeEvent");
+        if (!mHeadsetService.dialOutgoingCall(mDevice, dialNumber)) {
+            Log.w(TAG, "processDialCall, failed to dial in service");
+            mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
             return;
         }
+        mNeedDialingOutReply = true;
+    }
 
+    private void processVrEvent(int state) {
+        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
+            if (!mHeadsetService.startVoiceRecognitionByHeadset(mDevice)) {
+                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+            }
+        } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
+            if (mHeadsetService.stopVoiceRecognitionByHeadset(mDevice)) {
+                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
+            } else {
+                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+            }
+        } else {
+            mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        }
+    }
+
+    private void processVolumeEvent(int volumeType, int volume) {
+        // Only current active device can change SCO volume
+        if (!mDevice.equals(mHeadsetService.getActiveDevice())) {
+            Log.w(TAG, "processVolumeEvent, ignored because " + mDevice + " is not active");
+            return;
+        }
         if (volumeType == HeadsetHalConstants.VOLUME_TYPE_SPK) {
-            mPhoneState.setSpeakerVolume(volume);
+            mSpeakerVolume = volume;
             int flag = (getCurrentState() == mAudioOn) ? AudioManager.FLAG_SHOW_UI : 0;
-            mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
+            mSystemInterface.getAudioManager()
+                    .setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, volume, flag);
         } else if (volumeType == HeadsetHalConstants.VOLUME_TYPE_MIC) {
-            mPhoneState.setMicVolume(volume);
+            // Not used currently
+            mMicVolume = volume;
         } else {
-            Log.e(TAG, "Bad voluem type: " + volumeType);
+            Log.e(TAG, "Bad volume type: " + volumeType);
         }
     }
 
-    private void processSendDtmf(int dtmf, BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processSendDtmf device is null");
-            return;
-        }
-
-        if (mPhoneProxy != null) {
-            try {
-                mPhoneProxy.sendDtmf(dtmf);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        } else {
-            Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
+    private void processNoiseReductionEvent(boolean enable) {
+        String prevNrec = mAudioParams.getOrDefault(HEADSET_NREC, HEADSET_AUDIO_FEATURE_OFF);
+        String newNrec = enable ? HEADSET_AUDIO_FEATURE_ON : HEADSET_AUDIO_FEATURE_OFF;
+        mAudioParams.put(HEADSET_NREC, newNrec);
+        log("processNoiseReductionEvent: " + HEADSET_NREC + " change " + prevNrec + " -> "
+                + newNrec);
+        if (getAudioState() == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+            setAudioParameters();
         }
     }
 
-    private void processCallState(HeadsetCallState callState) {
-        processCallState(callState, false);
-    }
-
-    private void processCallState(HeadsetCallState callState, boolean isVirtualCall) {
-        mPhoneState.setNumActiveCall(callState.mNumActive);
-        mPhoneState.setNumHeldCall(callState.mNumHeld);
-        mPhoneState.setCallState(callState.mCallState);
-        if (mDialingOut) {
-            if (callState.mCallState == HeadsetHalConstants.CALL_STATE_DIALING) {
-                BluetoothDevice device = getDeviceForMessage(DIALING_OUT_TIMEOUT);
-                if (device == null) {
-                    return;
-                }
-                atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(device));
-                removeMessages(DIALING_OUT_TIMEOUT);
-            } else if (callState.mCallState == HeadsetHalConstants.CALL_STATE_ACTIVE
-                    || callState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE) {
-                mDialingOut = false;
-            }
+    private void processWBSEvent(int wbsConfig) {
+        String prevWbs = mAudioParams.getOrDefault(HEADSET_WBS, HEADSET_AUDIO_FEATURE_OFF);
+        switch (wbsConfig) {
+            case HeadsetHalConstants.BTHF_WBS_YES:
+                mAudioParams.put(HEADSET_WBS, HEADSET_AUDIO_FEATURE_ON);
+                break;
+            case HeadsetHalConstants.BTHF_WBS_NO:
+            case HeadsetHalConstants.BTHF_WBS_NONE:
+                mAudioParams.put(HEADSET_WBS, HEADSET_AUDIO_FEATURE_OFF);
+                break;
+            default:
+                Log.e(TAG, "processWBSEvent: unknown wbsConfig " + wbsConfig);
+                return;
         }
-
-        /* Set ActiveScoDevice to null when call ends */
-        if ((mActiveScoDevice != null) && !isInCall()
-                && callState.mCallState == HeadsetHalConstants.CALL_STATE_IDLE)
-            mActiveScoDevice = null;
-
-        log("mNumActive: " + callState.mNumActive + " mNumHeld: " + callState.mNumHeld
-                + " mCallState: " + callState.mCallState);
-        log("mNumber: " + callState.mNumber + " mType: " + callState.mType);
-
-        if (isVirtualCall) {
-            // virtual call state update
-            if (getCurrentState() != mDisconnected) {
-                phoneStateChangeNative(callState.mNumActive, callState.mNumHeld,
-                        callState.mCallState, callState.mNumber, callState.mType);
-            }
-        } else {
-            // circuit-switch voice call update
-            // stop virtual voice call if there is a CSV call ongoing
-            if (callState.mNumActive > 0 || callState.mNumHeld > 0
-                    || callState.mCallState != HeadsetHalConstants.CALL_STATE_IDLE) {
-                terminateScoUsingVirtualVoiceCall();
-            }
-
-            // Specific handling for case of starting MO/MT call while VOIP
-            // ongoing, terminateScoUsingVirtualVoiceCall() resets callState
-            // INCOMING/DIALING to IDLE. Some HS send AT+CIND? to read call
-            // and get wrong value of callsetup. This case is hit only
-            // SCO for VOIP call is not terminated via SDK API call.
-            if (mPhoneState.getCallState() != callState.mCallState) {
-                mPhoneState.setCallState(callState.mCallState);
-            }
-
-            // at this step: if there is virtual call ongoing, it means there is no CSV call
-            // let virtual call continue and skip phone state update
-            if (!isVirtualCallInProgress()) {
-                if (getCurrentState() != mDisconnected) {
-                    phoneStateChangeNative(callState.mNumActive, callState.mNumHeld,
-                            callState.mCallState, callState.mNumber, callState.mType);
-                }
-            }
-        }
-    }
-
-    // 1 enable noice reduction
-    // 0 disable noice reduction
-    private void processNoiceReductionEvent(int enable, BluetoothDevice device) {
-        HashMap<String, Integer> AudioParamNrec = mHeadsetAudioParam.get(device);
-        if (AudioParamNrec != null && !AudioParamNrec.isEmpty()) {
-            if (enable == 1)
-                AudioParamNrec.put("NREC", 1);
-            else
-                AudioParamNrec.put("NREC", 0);
-            log("NREC value for device :" + device + " is: " + AudioParamNrec.get("NREC"));
-        } else {
-            Log.e(TAG, "processNoiceReductionEvent: AudioParamNrec is null ");
-        }
-
-        if (mActiveScoDevice != null && mActiveScoDevice.equals(device)
-                && mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
-            setAudioParameters(device);
-        }
-    }
-
-    // 2 - WBS on
-    // 1 - NBS on
-    private void processWBSEvent(int enable, BluetoothDevice device) {
-        if (enable == 2) {
-            Log.d(TAG,
-                    "AudioManager.setParameters: bt_wbs=on, device=" + device.getName() + "["
-                            + device.getAddress() + "]");
-            mAudioManager.setParameters(HEADSET_WBS + "=on");
-        } else {
-            Log.d(TAG,
-                    "AudioManager.setParameters: bt_wbs=off, enable=" + enable
-                            + ", device=" + device.getName() + "[" + device.getAddress() + "]");
-            mAudioManager.setParameters(HEADSET_WBS + "=off");
-        }
+        log("processWBSEvent: " + HEADSET_NREC + " change " + prevWbs + " -> " + mAudioParams.get(
+                HEADSET_WBS));
     }
 
     private void processAtChld(int chld, BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processAtChld device is null");
-            return;
-        }
-
-        if (mPhoneProxy != null) {
-            try {
-                if (mPhoneProxy.processChld(chld)) {
-                    atResponseCodeNative(
-                            HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(device));
-                } else {
-                    atResponseCodeNative(
-                            HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                atResponseCodeNative(
-                        HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-            }
+        if (mSystemInterface.processChld(chld)) {
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
         } else {
-            Log.e(TAG, "Handsfree phone proxy null for At+Chld");
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
         }
     }
 
     private void processSubscriberNumberRequest(BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processSubscriberNumberRequest device is null");
-            return;
-        }
-
-        if (mPhoneProxy != null) {
-            try {
-                String number = mPhoneProxy.getSubscriberNumber();
-                if (number != null) {
-                    atResponseStringNative("+CNUM: ,\"" + number + "\","
-                                    + PhoneNumberUtils.toaFromString(number) + ",,4",
-                            getByteAddress(device));
-                    atResponseCodeNative(
-                            HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(device));
-                } else {
-                    Log.e(TAG, "getSubscriberNumber returns null");
-                    atResponseCodeNative(
-                            HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                atResponseCodeNative(
-                        HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-            }
+        String number = mSystemInterface.getSubscriberNumber();
+        if (number != null) {
+            mNativeInterface.atResponseString(device,
+                    "+CNUM: ,\"" + number + "\"," + PhoneNumberUtils.toaFromString(number) + ",,4");
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
         } else {
-            Log.e(TAG, "Handsfree phone proxy null for At+CNUM");
+            Log.e(TAG, "getSubscriberNumber returns null");
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
         }
     }
 
     private void processAtCind(BluetoothDevice device) {
-        int call, call_setup;
-
-        if (device == null) {
-            Log.w(TAG, "processAtCind device is null");
-            return;
-        }
+        int call, callSetup;
+        final HeadsetPhoneState phoneState = mSystemInterface.getHeadsetPhoneState();
 
         /* Handsfree carkits expect that +CIND is properly responded to
          Hence we ensure that a proper response is sent
          for the virtual call too.*/
-        if (isVirtualCallInProgress()) {
+        if (mHeadsetService.isVirtualCallStarted()) {
             call = 1;
-            call_setup = 0;
+            callSetup = 0;
         } else {
             // regular phone call
-            call = mPhoneState.getNumActiveCall();
-            call_setup = mPhoneState.getNumHeldCall();
+            call = phoneState.getNumActiveCall();
+            callSetup = phoneState.getNumHeldCall();
         }
 
-        cindResponseNative(mPhoneState.getService(), call, call_setup, mPhoneState.getCallState(),
-                mPhoneState.getSignal(), mPhoneState.getRoam(), mPhoneState.getBatteryCharge(),
-                getByteAddress(device));
+        mNativeInterface.cindResponse(device, phoneState.getCindService(), call, callSetup,
+                phoneState.getCallState(), phoneState.getCindSignal(), phoneState.getCindRoam(),
+                phoneState.getCindBatteryCharge());
     }
 
     private void processAtCops(BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processAtCops device is null");
-            return;
+        String operatorName = mSystemInterface.getNetworkOperator();
+        if (operatorName == null) {
+            operatorName = "";
         }
-
-        if (mPhoneProxy != null) {
-            try {
-                String operatorName = mPhoneProxy.getNetworkOperator();
-                if (operatorName == null) {
-                    operatorName = "";
-                }
-                copsResponseNative(operatorName, getByteAddress(device));
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                copsResponseNative("", getByteAddress(device));
-            }
-        } else {
-            Log.e(TAG, "Handsfree phone proxy null for At+COPS");
-            copsResponseNative("", getByteAddress(device));
-        }
+        mNativeInterface.copsResponse(device, operatorName);
     }
 
     private void processAtClcc(BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processAtClcc device is null");
-            return;
-        }
-
-        if (mPhoneProxy != null) {
-            try {
-                if (isVirtualCallInProgress()) {
-                    String phoneNumber = "";
-                    int type = PhoneNumberUtils.TOA_Unknown;
-                    try {
-                        phoneNumber = mPhoneProxy.getSubscriberNumber();
-                        type = PhoneNumberUtils.toaFromString(phoneNumber);
-                    } catch (RemoteException ee) {
-                        Log.e(TAG, "Unable to retrieve phone number"
-                                        + "using IBluetoothHeadsetPhone proxy");
-                        phoneNumber = "";
-                    }
-                    clccResponseNative(
-                            1, 0, 0, 0, false, phoneNumber, type, getByteAddress(device));
-                    clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
-                } else if (!mPhoneProxy.listCurrentCalls()) {
-                    clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
-                } else {
-                    Log.d(TAG, "Starting CLCC response timeout for device: " + device);
-                    Message m = obtainMessage(CLCC_RSP_TIMEOUT);
-                    m.obj = getMatchingDevice(device);
-                    sendMessageDelayed(m, CLCC_RSP_TIMEOUT_VALUE);
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
+        if (mHeadsetService.isVirtualCallStarted()) {
+            // In virtual call, send our phone number instead of remote phone number
+            String phoneNumber = mSystemInterface.getSubscriberNumber();
+            if (phoneNumber == null) {
+                phoneNumber = "";
             }
+            int type = PhoneNumberUtils.toaFromString(phoneNumber);
+            mNativeInterface.clccResponse(device, 1, 0, 0, 0, false, phoneNumber, type);
+            mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0);
         } else {
-            Log.e(TAG, "Handsfree phone proxy null for At+CLCC");
-            clccResponseNative(0, 0, 0, 0, false, "", 0, getByteAddress(device));
+            // In Telecom call, ask Telecom to send send remote phone number
+            if (!mSystemInterface.listCurrentCalls()) {
+                Log.e(TAG, "processAtClcc: failed to list current calls for " + device);
+                mNativeInterface.clccResponse(device, 0, 0, 0, 0, false, "", 0);
+            } else {
+                sendMessageDelayed(CLCC_RSP_TIMEOUT, device, CLCC_RSP_TIMEOUT_MS);
+            }
         }
     }
 
@@ -2853,7 +1680,7 @@
             mPhonebook.handleCscsCommand(atString, type, device);
         } else {
             Log.e(TAG, "Phonebook handle null for At+CSCS");
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
         }
     }
 
@@ -2863,7 +1690,7 @@
             mPhonebook.handleCpbsCommand(atString, type, device);
         } else {
             Log.e(TAG, "Phonebook handle null for At+CPBS");
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
         }
     }
 
@@ -2873,19 +1700,7 @@
             mPhonebook.handleCpbrCommand(atString, type, device);
         } else {
             Log.e(TAG, "Phonebook handle null for At+CPBR");
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-        }
-    }
-
-    private void queryPhoneState() {
-        if (mPhoneProxy != null) {
-            try {
-                mPhoneProxy.queryPhoneState();
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        } else {
-            Log.e(TAG, "Handsfree phone proxy null for query phone state");
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
         }
     }
 
@@ -2893,7 +1708,7 @@
      * Find a character ch, ignoring quoted sections.
      * Return input.length() if not found.
      */
-    static private int findChar(char ch, String input, int fromIndex) {
+    private static int findChar(char ch, String input, int fromIndex) {
         for (int i = fromIndex; i < input.length(); i++) {
             char c = input.charAt(i);
             if (c == '"') {
@@ -2913,7 +1728,7 @@
      * Integer arguments are turned into Integer objects. Otherwise a String
      * object is used.
      */
-    static private Object[] generateArgs(String input) {
+    private static Object[] generateArgs(String input) {
         int i = 0;
         int j;
         ArrayList<Object> out = new ArrayList<Object>();
@@ -2934,6 +1749,7 @@
 
     /**
      * Process vendor specific AT commands
+     *
      * @param atString AT command after the "AT+" prefix
      * @param device Remote device that has sent this command
      */
@@ -2943,23 +1759,23 @@
         // Currently we accept only SET type commands.
         int indexOfEqual = atString.indexOf("=");
         if (indexOfEqual == -1) {
-            Log.e(TAG, "processVendorSpecificAt: command type error in " + atString);
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+            Log.w(TAG, "processVendorSpecificAt: command type error in " + atString);
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
             return;
         }
 
         String command = atString.substring(0, indexOfEqual);
         Integer companyId = VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.get(command);
         if (companyId == null) {
-            Log.e(TAG, "processVendorSpecificAt: unsupported command: " + atString);
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+            Log.i(TAG, "processVendorSpecificAt: unsupported command: " + atString);
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
             return;
         }
 
         String arg = atString.substring(indexOfEqual + 1);
         if (arg.startsWith("?")) {
-            Log.e(TAG, "processVendorSpecificAt: command type error in " + atString);
-            atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
+            Log.w(TAG, "processVendorSpecificAt: command type error in " + atString);
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
             return;
         }
 
@@ -2967,13 +1783,14 @@
         if (command.equals(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL)) {
             processAtXapl(args, device);
         }
-        broadcastVendorSpecificEventIntent(
-                command, companyId, BluetoothHeadset.AT_CMD_TYPE_SET, args, device);
-        atResponseCodeNative(HeadsetHalConstants.AT_RESPONSE_OK, 0, getByteAddress(device));
+        broadcastVendorSpecificEventIntent(command, companyId, BluetoothHeadset.AT_CMD_TYPE_SET,
+                args, device);
+        mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
     }
 
     /**
      * Process AT+XAPL AT command
+     *
      * @param args command arguments after the equal sign
      * @param device Remote device that has sent this command
      */
@@ -2987,7 +1804,7 @@
             return;
         }
         // feature = 2 indicates that we support battery level reporting only
-        atResponseStringNative("+XAPL=iPhone," + String.valueOf(2), getByteAddress(device));
+        mNativeInterface.atResponseString(device, "+XAPL=iPhone," + String.valueOf(2));
     }
 
     private void processUnknownAt(String atString, BluetoothDevice device) {
@@ -3009,90 +1826,83 @@
         }
     }
 
+    // HSP +CKPD command
     private void processKeyPressed(BluetoothDevice device) {
-        if (device == null) {
-            Log.w(TAG, "processKeyPressed device is null");
-            return;
-        }
-
-        if (mPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) {
-            if (mPhoneProxy != null) {
-                try {
-                    mPhoneProxy.answerCall();
-                } catch (RemoteException e) {
-                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
+        if (mSystemInterface.isRinging()) {
+            mSystemInterface.answerCall(device);
+        } else if (mSystemInterface.isInCall()) {
+            if (getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                // Should connect audio as well
+                if (!mHeadsetService.setActiveDevice(mDevice)) {
+                    Log.w(TAG, "processKeyPressed, failed to set active device to " + mDevice);
                 }
             } else {
-                Log.e(TAG, "Handsfree phone proxy null for answering call");
+                mSystemInterface.hangupCall(device);
             }
-        } else if (mPhoneState.getNumActiveCall() > 0) {
-            if (!isAudioOn()) {
-                connectAudioNative(getByteAddress(mCurrentDevice));
-            } else {
-                if (mPhoneProxy != null) {
-                    try {
-                        mPhoneProxy.hangupCall();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                    }
-                } else {
-                    Log.e(TAG, "Handsfree phone proxy null for hangup call");
-                }
+        } else if (getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+            if (!mNativeInterface.disconnectAudio(mDevice)) {
+                Log.w(TAG, "processKeyPressed, failed to disconnect audio from " + mDevice);
             }
         } else {
-            String dialNumber = mPhonebook.getLastDialledNumber();
-            if (dialNumber == null) {
-                log("processKeyPressed, last dial number null");
+            // We have already replied OK to this HSP command, no feedback is needed
+            if (mHeadsetService.hasDeviceInitiatedDialingOut()) {
+                Log.w(TAG, "processKeyPressed, already dialling");
                 return;
             }
-            Intent intent = new Intent(
-                    Intent.ACTION_CALL_PRIVILEGED, Uri.fromParts(SCHEME_TEL, dialNumber, null));
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            mService.startActivity(intent);
+            String dialNumber = mPhonebook.getLastDialledNumber();
+            if (dialNumber == null) {
+                Log.w(TAG, "processKeyPressed, last dial number null");
+                return;
+            }
+            if (!mHeadsetService.dialOutgoingCall(mDevice, dialNumber)) {
+                Log.w(TAG, "processKeyPressed, failed to call in service");
+                return;
+            }
         }
     }
 
     /**
      * Send HF indicator value changed intent
+     *
      * @param device Device whose HF indicator value has changed
-     * @param ind_id Indicator ID [0-65535]
-     * @param ind_value Indicator Value [0-65535], -1 means invalid but ind_id is supported
+     * @param indId Indicator ID [0-65535]
+     * @param indValue Indicator Value [0-65535], -1 means invalid but indId is supported
      */
-    private void sendIndicatorIntent(BluetoothDevice device, int ind_id, int ind_value) {
+    private void sendIndicatorIntent(BluetoothDevice device, int indId, int indValue) {
         Intent intent = new Intent(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, ind_id);
-        intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, ind_value);
+        intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, indId);
+        intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, indValue);
 
-        mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
+        mHeadsetService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
     }
 
-    private void processAtBind(String at_string, BluetoothDevice device) {
-        log("processAtBind: " + at_string);
+    private void processAtBind(String atString, BluetoothDevice device) {
+        log("processAtBind: " + atString);
 
         // Parse the AT String to find the Indicator Ids that are supported
-        int ind_id = 0;
+        int indId = 0;
         int iter = 0;
         int iter1 = 0;
 
-        while (iter < at_string.length()) {
-            iter1 = findChar(',', at_string, iter);
-            String id = at_string.substring(iter, iter1);
+        while (iter < atString.length()) {
+            iter1 = findChar(',', atString, iter);
+            String id = atString.substring(iter, iter1);
 
             try {
-                ind_id = Integer.valueOf(id);
+                indId = Integer.valueOf(id);
             } catch (NumberFormatException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
 
-            switch (ind_id) {
+            switch (indId) {
                 case HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY:
                     log("Send Broadcast intent for the Enhanced Driver Safety indicator.");
-                    sendIndicatorIntent(device, ind_id, -1);
+                    sendIndicatorIntent(device, indId, -1);
                     break;
                 case HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS:
                     log("Send Broadcast intent for the Battery Level indicator.");
-                    sendIndicatorIntent(device, ind_id, -1);
+                    sendIndicatorIntent(device, indId, -1);
                     break;
                 default:
                     log("Invalid HF Indicator Received");
@@ -3108,160 +1918,15 @@
         sendIndicatorIntent(device, indId, indValue);
     }
 
-    private void onConnectionStateChanged(int state, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
-        event.valueInt = state;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAudioStateChanged(int state, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
-        event.valueInt = state;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onVrStateChanged(int state, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_VR_STATE_CHANGED);
-        event.valueInt = state;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAnswerCall(byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_ANSWER_CALL);
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onHangupCall(byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_HANGUP_CALL);
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onVolumeChanged(int type, int volume, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_VOLUME_CHANGED);
-        event.valueInt = type;
-        event.valueInt2 = volume;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onDialCall(String number, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_DIAL_CALL);
-        event.valueString = number;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onSendDtmf(int dtmf, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_SEND_DTMF);
-        event.valueInt = dtmf;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onNoiceReductionEnable(boolean enable, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_NOICE_REDUCTION);
-        event.valueInt = enable ? 1 : 0;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onWBS(int codec, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_WBS);
-        event.valueInt = codec;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAtChld(int chld, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_AT_CHLD);
-        event.valueInt = chld;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAtCnum(byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST);
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAtCind(byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_AT_CIND);
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAtCops(byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_AT_COPS);
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onAtClcc(byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_AT_CLCC);
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onUnknownAt(String atString, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_UNKNOWN_AT);
-        event.valueString = atString;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onKeyPressed(byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_KEY_PRESSED);
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onATBind(String atString, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_BIND);
-        event.valueString = atString;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void onATBiev(int ind_id, int ind_value, byte[] address) {
-        StackEvent event = new StackEvent(EVENT_TYPE_BIEV);
-        event.valueInt = ind_id;
-        event.valueInt2 = ind_value;
-        event.device = getDevice(address);
-        sendMessage(STACK_EVENT, event);
-    }
-
-    private void processIntentBatteryChanged(Intent intent) {
-        int batteryLevel = intent.getIntExtra("level", -1);
-        int scale = intent.getIntExtra("scale", -1);
-        if (batteryLevel == -1 || scale == -1 || scale == 0) {
-            Log.e(TAG, "Bad Battery Changed intent: " + batteryLevel + "," + scale);
-            return;
-        }
-        batteryLevel = batteryLevel * 5 / scale;
-        mPhoneState.setBatteryCharge(batteryLevel);
-    }
-
-    private void processDeviceStateChanged(HeadsetDeviceState deviceState) {
-        notifyDeviceStatusNative(deviceState.mService, deviceState.mRoam, deviceState.mSignal,
-                deviceState.mBatteryCharge);
-    }
-
     private void processSendClccResponse(HeadsetClccResponse clcc) {
-        BluetoothDevice device = getDeviceForMessage(CLCC_RSP_TIMEOUT);
-        if (device == null) {
+        if (!hasMessages(CLCC_RSP_TIMEOUT)) {
             return;
         }
         if (clcc.mIndex == 0) {
             removeMessages(CLCC_RSP_TIMEOUT);
         }
-        clccResponseNative(clcc.mIndex, clcc.mDirection, clcc.mStatus, clcc.mMode, clcc.mMpty,
-                clcc.mNumber, clcc.mType, getByteAddress(device));
+        mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection, clcc.mStatus,
+                clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
     }
 
     private void processSendVendorSpecificResultCode(HeadsetVendorSpecificResultCode resultCode) {
@@ -3269,76 +1934,33 @@
         if (resultCode.mArg != null) {
             stringToSend += resultCode.mArg;
         }
-        atResponseStringNative(stringToSend, getByteAddress(resultCode.mDevice));
+        mNativeInterface.atResponseString(resultCode.mDevice, stringToSend);
     }
 
-    private String getCurrentDeviceName(BluetoothDevice device) {
-        String defaultName = "<unknown>";
-
-        if (device == null) {
-            return defaultName;
-        }
-
-        String deviceName = device.getName();
+    private String getCurrentDeviceName() {
+        String deviceName = mAdapterService.getRemoteName(mDevice);
         if (deviceName == null) {
-            return defaultName;
+            return "<unknown>";
         }
         return deviceName;
     }
 
-    private byte[] getByteAddress(BluetoothDevice device) {
-        return Utils.getBytesFromAddress(device.getAddress());
-    }
-
-    private BluetoothDevice getDevice(byte[] address) {
-        return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
-    }
-
-    private boolean isInCall() {
-        return ((mPhoneState.getNumActiveCall() > 0) || (mPhoneState.getNumHeldCall() > 0)
-                || ((mPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE)
-                           && (mPhoneState.getCallState()
-                                      != HeadsetHalConstants.CALL_STATE_INCOMING)));
-    }
-
-    private boolean isRinging() {
-        return mPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING;
-    }
-
-    // Accept incoming SCO only when there is in-band ringing, incoming call,
-    // active call, VR activated, active VOIP call
-    private boolean isScoAcceptable() {
-        if (mForceScoAudio) return true;
-        return mAudioRouteAllowed
-                && (mVoiceRecognitionStarted || isInCall()
-                           || (BluetoothHeadset.isInbandRingingSupported(mService) && isRinging()));
-    }
-
-    boolean isConnected() {
-        IState currentState = getCurrentState();
-        return (currentState == mConnected || currentState == mAudioOn);
-    }
-
-    boolean okToConnect(BluetoothDevice device) {
-        AdapterService adapterService = AdapterService.getAdapterService();
-        int priority = mService.getPriority(device);
-        boolean ret = false;
-        // check if this is an incoming connection in Quiet mode.
-        if ((adapterService == null)
-                || ((adapterService.isQuietModeEnabled() == true) && (mTargetDevice == null))) {
-            ret = false;
+    private void updateAgIndicatorEnableState(
+            HeadsetAgIndicatorEnableState agIndicatorEnableState) {
+        if (Objects.equals(mAgIndicatorEnableState, agIndicatorEnableState)) {
+            Log.i(TAG, "updateAgIndicatorEnableState, no change in indicator state "
+                    + mAgIndicatorEnableState);
+            return;
         }
-        // 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
-        else if ((BluetoothProfile.PRIORITY_OFF < priority)
-                || ((BluetoothProfile.PRIORITY_UNDEFINED == priority)
-                           && (device.getBondState() != BluetoothDevice.BOND_NONE))) {
-            if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
-                ret = true;
-            }
+        mAgIndicatorEnableState = agIndicatorEnableState;
+        int events = PhoneStateListener.LISTEN_NONE;
+        if (mAgIndicatorEnableState != null && mAgIndicatorEnableState.service) {
+            events |= PhoneStateListener.LISTEN_SERVICE_STATE;
         }
-        return ret;
+        if (mAgIndicatorEnableState != null && mAgIndicatorEnableState.signal) {
+            events |= PhoneStateListener.LISTEN_SIGNAL_STRENGTHS;
+        }
+        mSystemInterface.getHeadsetPhoneState().listenForPhoneState(mDevice, events);
     }
 
     @Override
@@ -3348,113 +1970,98 @@
         }
     }
 
-    public void handleAccessPermissionResult(Intent intent) {
+    @Override
+    protected String getLogRecString(Message msg) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(getMessageName(msg.what));
+        builder.append(": ");
+        builder.append("arg1=")
+                .append(msg.arg1)
+                .append(", arg2=")
+                .append(msg.arg2)
+                .append(", obj=");
+        if (msg.obj instanceof HeadsetMessageObject) {
+            HeadsetMessageObject object = (HeadsetMessageObject) msg.obj;
+            object.buildString(builder);
+        } else {
+            builder.append(msg.obj);
+        }
+        return builder.toString();
+    }
+
+    private void handleAccessPermissionResult(Intent intent) {
         log("handleAccessPermissionResult");
         BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-        if (mPhonebook != null) {
-            if (!mPhonebook.getCheckingAccessPermission()) {
-                return;
-            }
-            int atCommandResult = 0;
-            int atCommandErrorCode = 0;
-            // HeadsetBase headset = mHandsfree.getHeadset();
-            // ASSERT: (headset != null) && headSet.isConnected()
-            // REASON: mCheckingAccessPermission is true, otherwise resetAtState
-            // has set mCheckingAccessPermission to false
-            if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
-                if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
-                            BluetoothDevice.CONNECTION_ACCESS_NO)
-                        == BluetoothDevice.CONNECTION_ACCESS_YES) {
-                    if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
-                        mCurrentDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
-                    }
-                    atCommandResult = mPhonebook.processCpbrCommand(device);
-                } else {
-                    if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
-                        mCurrentDevice.setPhonebookAccessPermission(
-                                BluetoothDevice.ACCESS_REJECTED);
-                    }
+        if (!mPhonebook.getCheckingAccessPermission()) {
+            return;
+        }
+        int atCommandResult = 0;
+        int atCommandErrorCode = 0;
+        // HeadsetBase headset = mHandsfree.getHeadset();
+        // ASSERT: (headset != null) && headSet.isConnected()
+        // REASON: mCheckingAccessPermission is true, otherwise resetAtState
+        // has set mCheckingAccessPermission to false
+        if (intent.getAction().equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+                    BluetoothDevice.CONNECTION_ACCESS_NO)
+                    == BluetoothDevice.CONNECTION_ACCESS_YES) {
+                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+                }
+                atCommandResult = mPhonebook.processCpbrCommand(device);
+            } else {
+                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
+                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
                 }
             }
-            mPhonebook.setCpbrIndex(-1);
-            mPhonebook.setCheckingAccessPermission(false);
-
-            if (atCommandResult >= 0) {
-                atResponseCodeNative(atCommandResult, atCommandErrorCode, getByteAddress(device));
-            } else {
-                log("handleAccessPermissionResult - RESULT_NONE");
-            }
+        }
+        mPhonebook.setCpbrIndex(-1);
+        mPhonebook.setCheckingAccessPermission(false);
+        if (atCommandResult >= 0) {
+            mNativeInterface.atResponseCode(device, atCommandResult, atCommandErrorCode);
         } else {
-            Log.e(TAG, "Phonebook handle null");
-            if (device != null) {
-                atResponseCodeNative(
-                        HeadsetHalConstants.AT_RESPONSE_ERROR, 0, getByteAddress(device));
-            }
+            log("handleAccessPermissionResult - RESULT_NONE");
         }
     }
 
-    private static final String SCHEME_TEL = "tel";
-
-    // Event types for STACK_EVENT message
-    final private static int EVENT_TYPE_NONE = 0;
-    final private static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
-    final private static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
-    final private static int EVENT_TYPE_VR_STATE_CHANGED = 3;
-    final private static int EVENT_TYPE_ANSWER_CALL = 4;
-    final private static int EVENT_TYPE_HANGUP_CALL = 5;
-    final private static int EVENT_TYPE_VOLUME_CHANGED = 6;
-    final private static int EVENT_TYPE_DIAL_CALL = 7;
-    final private static int EVENT_TYPE_SEND_DTMF = 8;
-    final private static int EVENT_TYPE_NOICE_REDUCTION = 9;
-    final private static int EVENT_TYPE_AT_CHLD = 10;
-    final private static int EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST = 11;
-    final private static int EVENT_TYPE_AT_CIND = 12;
-    final private static int EVENT_TYPE_AT_COPS = 13;
-    final private static int EVENT_TYPE_AT_CLCC = 14;
-    final private static int EVENT_TYPE_UNKNOWN_AT = 15;
-    final private static int EVENT_TYPE_KEY_PRESSED = 16;
-    final private static int EVENT_TYPE_WBS = 17;
-    final private static int EVENT_TYPE_BIND = 18;
-    final private static int EVENT_TYPE_BIEV = 19;
-
-    private class StackEvent {
-        int type = EVENT_TYPE_NONE;
-        int valueInt = 0;
-        int valueInt2 = 0;
-        String valueString = null;
-        BluetoothDevice device = null;
-
-        private StackEvent(int type) {
-            this.type = type;
+    private static String getMessageName(int what) {
+        switch (what) {
+            case CONNECT:
+                return "CONNECT";
+            case DISCONNECT:
+                return "DISCONNECT";
+            case CONNECT_AUDIO:
+                return "CONNECT_AUDIO";
+            case DISCONNECT_AUDIO:
+                return "DISCONNECT_AUDIO";
+            case VOICE_RECOGNITION_START:
+                return "VOICE_RECOGNITION_START";
+            case VOICE_RECOGNITION_STOP:
+                return "VOICE_RECOGNITION_STOP";
+            case INTENT_SCO_VOLUME_CHANGED:
+                return "INTENT_SCO_VOLUME_CHANGED";
+            case INTENT_CONNECTION_ACCESS_REPLY:
+                return "INTENT_CONNECTION_ACCESS_REPLY";
+            case CALL_STATE_CHANGED:
+                return "CALL_STATE_CHANGED";
+            case DEVICE_STATE_CHANGED:
+                return "DEVICE_STATE_CHANGED";
+            case SEND_CCLC_RESPONSE:
+                return "SEND_CCLC_RESPONSE";
+            case SEND_VENDOR_SPECIFIC_RESULT_CODE:
+                return "SEND_VENDOR_SPECIFIC_RESULT_CODE";
+            case STACK_EVENT:
+                return "STACK_EVENT";
+            case VOICE_RECOGNITION_RESULT:
+                return "VOICE_RECOGNITION_RESULT";
+            case DIALING_OUT_RESULT:
+                return "DIALING_OUT_RESULT";
+            case CLCC_RSP_TIMEOUT:
+                return "CLCC_RSP_TIMEOUT";
+            case CONNECT_TIMEOUT:
+                return "CONNECT_TIMEOUT";
+            default:
+                return "UNKNOWN(" + what + ")";
         }
     }
-
-    /*package*/ native boolean atResponseCodeNative(
-            int responseCode, int errorCode, byte[] address);
-    /*package*/ native boolean atResponseStringNative(String responseString, byte[] address);
-
-    private native static void classInitNative();
-    private native void initializeNative(int max_hf_clients, boolean inband_ring_enable);
-    private native void cleanupNative();
-    private native boolean connectHfpNative(byte[] address);
-    private native boolean disconnectHfpNative(byte[] address);
-    private native boolean connectAudioNative(byte[] address);
-    private native boolean disconnectAudioNative(byte[] address);
-    private native boolean startVoiceRecognitionNative(byte[] address);
-    private native boolean stopVoiceRecognitionNative(byte[] address);
-    private native boolean setVolumeNative(int volumeType, int volume, byte[] address);
-    private native boolean cindResponseNative(int service, int numActive, int numHeld,
-            int callState, int signal, int roam, int batteryCharge, byte[] address);
-    private native boolean bindResponseNative(int ind_id, boolean ind_status, byte[] address);
-    private native boolean notifyDeviceStatusNative(
-            int networkState, int serviceType, int signal, int batteryCharge);
-
-    private native boolean clccResponseNative(int index, int dir, int status, int mode,
-            boolean mpty, String number, int type, byte[] address);
-    private native boolean copsResponseNative(String operatorName, byte[] address);
-
-    private native boolean phoneStateChangeNative(
-            int numActive, int numHeld, int callState, String number, int type);
-    private native boolean configureWBSNative(byte[] address, int condec_config);
-    private native boolean setScoAllowedNative(boolean value);
 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
new file mode 100644
index 0000000..81090eb
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright 2017 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.hfp;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.IBluetoothHeadsetPhone;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Defines system calls that is used by state machine/service to either send or receive
+ * messages from the Android System.
+ */
+@VisibleForTesting
+public class HeadsetSystemInterface {
+    private static final String TAG = HeadsetSystemInterface.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private final HeadsetService mHeadsetService;
+    private final AudioManager mAudioManager;
+    private final HeadsetPhoneState mHeadsetPhoneState;
+    private PowerManager.WakeLock mVoiceRecognitionWakeLock;
+    private volatile IBluetoothHeadsetPhone mPhoneProxy;
+    private final ServiceConnection mPhoneProxyConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) {
+                Log.d(TAG, "Proxy object connected");
+            }
+            synchronized (HeadsetSystemInterface.this) {
+                mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) {
+                Log.d(TAG, "Proxy object disconnected");
+            }
+            synchronized (HeadsetSystemInterface.this) {
+                mPhoneProxy = null;
+            }
+        }
+    };
+
+    HeadsetSystemInterface(HeadsetService headsetService) {
+        if (headsetService == null) {
+            Log.wtfStack(TAG, "HeadsetService parameter is null");
+        }
+        mHeadsetService = headsetService;
+        mAudioManager = (AudioManager) mHeadsetService.getSystemService(Context.AUDIO_SERVICE);
+        PowerManager powerManager =
+                (PowerManager) mHeadsetService.getSystemService(Context.POWER_SERVICE);
+        mVoiceRecognitionWakeLock =
+                powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG + ":VoiceRecognition");
+        mVoiceRecognitionWakeLock.setReferenceCounted(false);
+        mHeadsetPhoneState = new HeadsetPhoneState(mHeadsetService);
+    }
+
+    /**
+     * Initialize this system interface
+     */
+    public synchronized void init() {
+        // Bind to Telecom phone proxy service
+        Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());
+        intent.setComponent(intent.resolveSystemService(mHeadsetService.getPackageManager(), 0));
+        if (intent.getComponent() == null || !mHeadsetService.bindService(intent,
+                mPhoneProxyConnection, 0)) {
+            // Crash the stack if cannot bind to Telecom
+            Log.wtfStack(TAG, "Could not bind to IBluetoothHeadsetPhone Service, intent=" + intent);
+        }
+    }
+
+    /**
+     * Stop this system interface
+     */
+    public synchronized void stop() {
+        if (mPhoneProxy != null) {
+            if (DBG) {
+                Log.d(TAG, "Unbinding phone proxy");
+            }
+            mPhoneProxy = null;
+            // Synchronization should make sure unbind can be successful
+            mHeadsetService.unbindService(mPhoneProxyConnection);
+        }
+        mHeadsetPhoneState.cleanup();
+    }
+
+    /**
+     * Get audio manager. Most audio manager oprations are pass through and therefore are not
+     * individually managed by this class
+     *
+     * @return audio manager for setting audio parameters
+     */
+    @VisibleForTesting
+    public AudioManager getAudioManager() {
+        return mAudioManager;
+    }
+
+    /**
+     * Get wake lock for voice recognition
+     *
+     * @return wake lock for voice recognition
+     */
+    @VisibleForTesting
+    public PowerManager.WakeLock getVoiceRecognitionWakeLock() {
+        return mVoiceRecognitionWakeLock;
+    }
+
+    /**
+     * Get HeadsetPhoneState instance to interact with Telephony service
+     *
+     * @return HeadsetPhoneState interface to interact with Telephony service
+     */
+    @VisibleForTesting
+    public HeadsetPhoneState getHeadsetPhoneState() {
+        return mHeadsetPhoneState;
+    }
+
+    /**
+     * Answer the current incoming call in Telecom service
+     *
+     * @param device the Bluetooth device used for answering this call
+     */
+    @VisibleForTesting
+    public void answerCall(BluetoothDevice device) {
+        if (device == null) {
+            Log.w(TAG, "answerCall device is null");
+            return;
+        }
+
+        if (mPhoneProxy != null) {
+            try {
+                mHeadsetService.setActiveDevice(device);
+                mPhoneProxy.answerCall();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        } else {
+            Log.e(TAG, "Handsfree phone proxy null for answering call");
+        }
+    }
+
+    /**
+     * Hangup the current call, could either be Telecom call or virtual call
+     *
+     * @param device the Bluetooth device used for hanging up this call
+     */
+    @VisibleForTesting
+    public void hangupCall(BluetoothDevice device) {
+        if (device == null) {
+            Log.w(TAG, "hangupCall device is null");
+            return;
+        }
+        // Close the virtual call if active. Virtual call should be
+        // terminated for CHUP callback event
+        if (mHeadsetService.isVirtualCallStarted()) {
+            mHeadsetService.stopScoUsingVirtualVoiceCall();
+        } else {
+            if (mPhoneProxy != null) {
+                try {
+                    mPhoneProxy.hangupCall();
+                } catch (RemoteException e) {
+                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                }
+            } else {
+                Log.e(TAG, "Handsfree phone proxy null for hanging up call");
+            }
+        }
+    }
+
+    /**
+     * Instructs Telecom to play the specified DTMF tone for the current foreground call
+     *
+     * @param dtmf dtmf code
+     * @param device the Bluetooth device that sent this code
+     */
+    @VisibleForTesting
+    public boolean sendDtmf(int dtmf, BluetoothDevice device) {
+        if (device == null) {
+            Log.w(TAG, "sendDtmf device is null");
+            return false;
+        }
+        if (mPhoneProxy != null) {
+            try {
+                return mPhoneProxy.sendDtmf(dtmf);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        } else {
+            Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
+        }
+        return false;
+    }
+
+    /**
+     * Instructs Telecom hold an incoming call
+     *
+     * @param chld index of the call to hold
+     */
+    @VisibleForTesting
+    public boolean processChld(int chld) {
+        if (mPhoneProxy != null) {
+            try {
+                return mPhoneProxy.processChld(chld);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        } else {
+            Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
+        }
+        return false;
+    }
+
+    /**
+     * Get the the alphabetic name of current registered operator.
+     *
+     * @return null on error, empty string if not available
+     */
+    @VisibleForTesting
+    public String getNetworkOperator() {
+        final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
+        if (phoneProxy == null) {
+            Log.e(TAG, "getNetworkOperator() failed: mPhoneProxy is null");
+            return null;
+        }
+        try {
+            // Should never return null
+            return mPhoneProxy.getNetworkOperator();
+        } catch (RemoteException exception) {
+            Log.e(TAG, "getNetworkOperator() failed: " + exception.getMessage());
+            exception.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Get the phone number of this device
+     *
+     * @return null if unavailable
+     */
+    @VisibleForTesting
+    public String getSubscriberNumber() {
+        final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
+        if (phoneProxy == null) {
+            Log.e(TAG, "getSubscriberNumber() failed: mPhoneProxy is null");
+            return null;
+        }
+        try {
+            return mPhoneProxy.getSubscriberNumber();
+        } catch (RemoteException exception) {
+            Log.e(TAG, "getSubscriberNumber() failed: " + exception.getMessage());
+            exception.printStackTrace();
+            return null;
+        }
+    }
+
+
+    /**
+     * Ask the Telecomm service to list current list of calls through CLCC response
+     * {@link BluetoothHeadset#clccResponse(int, int, int, int, boolean, String, int)}
+     *
+     * @return
+     */
+    @VisibleForTesting
+    public boolean listCurrentCalls() {
+        final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
+        if (phoneProxy == null) {
+            Log.e(TAG, "listCurrentCalls() failed: mPhoneProxy is null");
+            return false;
+        }
+        try {
+            return mPhoneProxy.listCurrentCalls();
+        } catch (RemoteException exception) {
+            Log.e(TAG, "listCurrentCalls() failed: " + exception.getMessage());
+            exception.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Request Telecom service to send an update of the current call state to the headset service
+     * through {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int)}
+     */
+    @VisibleForTesting
+    public void queryPhoneState() {
+        final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
+        if (phoneProxy != null) {
+            try {
+                mPhoneProxy.queryPhoneState();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        } else {
+            Log.e(TAG, "Handsfree phone proxy null for query phone state");
+        }
+    }
+
+    /**
+     * Check if we are currently in a phone call
+     *
+     * @return True iff we are in a phone call
+     */
+    @VisibleForTesting
+    public boolean isInCall() {
+        return ((mHeadsetPhoneState.getNumActiveCall() > 0) || (mHeadsetPhoneState.getNumHeldCall()
+                > 0) || ((mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE)
+                && (mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_INCOMING)));
+    }
+
+    /**
+     * Check if there is currently an incoming call
+     *
+     * @return True iff there is an incoming call
+     */
+    @VisibleForTesting
+    public boolean isRinging() {
+        return mHeadsetPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING;
+    }
+
+    /**
+     * Check if call status is idle
+     *
+     * @return true if call state is neither ringing nor in call
+     */
+    @VisibleForTesting
+    public boolean isCallIdle() {
+        return !isInCall() && !isRinging();
+    }
+
+    /**
+     * Activate voice recognition on Android system
+     *
+     * @return true if activation succeeds, caller should wait for
+     * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} callback that will then
+     * trigger {@link HeadsetService#startVoiceRecognition(BluetoothDevice)}, false if failed to
+     * activate
+     */
+    @VisibleForTesting
+    public boolean activateVoiceRecognition() {
+        Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        try {
+            mHeadsetService.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            Log.e(TAG, "activateVoiceRecognition, failed due to activity not found for " + intent);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Deactivate voice recognition on Android system
+     *
+     * @return true if activation succeeds, caller should wait for
+     * {@link BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} callback that will then
+     * trigger {@link HeadsetService#stopVoiceRecognition(BluetoothDevice)}, false if failed to
+     * activate
+     */
+    @VisibleForTesting
+    public boolean deactivateVoiceRecognition() {
+        // TODO: need a method to deactivate voice recognition on Android
+        return true;
+    }
+
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetVendorSpecificResultCode.java b/src/com/android/bluetooth/hfp/HeadsetVendorSpecificResultCode.java
new file mode 100644
index 0000000..4acc211
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/HeadsetVendorSpecificResultCode.java
@@ -0,0 +1,45 @@
+/*
+ * 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.hfp;
+
+import android.bluetooth.BluetoothDevice;
+
+class HeadsetVendorSpecificResultCode extends HeadsetMessageObject {
+    BluetoothDevice mDevice;
+    String mCommand;
+    String mArg;
+
+    HeadsetVendorSpecificResultCode(BluetoothDevice device, String command, String arg) {
+        mDevice = device;
+        mCommand = command;
+        mArg = arg;
+    }
+
+    @Override
+    public void buildString(StringBuilder builder) {
+        if (builder == null) {
+            return;
+        }
+        builder.append(this.getClass().getSimpleName())
+                .append("[device=")
+                .append(mDevice)
+                .append(", command=")
+                .append(mCommand)
+                .append(", arg=")
+                .append(mArg)
+                .append("]");
+    }
+}
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java b/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
index ba9655d..88ae579 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
@@ -21,156 +21,156 @@
  * @hide
  */
 
-final public class HeadsetClientHalConstants {
+public final class HeadsetClientHalConstants {
     // Do not modify without updating the HAL bt_hf_client.h files.
 
     // match up with bthf_client_connection_state_t enum of bt_hf_client.h
-    final static int CONNECTION_STATE_DISCONNECTED = 0;
-    final static int CONNECTION_STATE_CONNECTING = 1;
-    final static int CONNECTION_STATE_CONNECTED = 2;
-    final static int CONNECTION_STATE_SLC_CONNECTED = 3;
-    final static int CONNECTION_STATE_DISCONNECTING = 4;
+    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_SLC_CONNECTED = 3;
+    static final int CONNECTION_STATE_DISCONNECTING = 4;
 
     // match up with bthf_client_audio_state_t enum of bt_hf_client.h
-    final static int AUDIO_STATE_DISCONNECTED = 0;
-    final static int AUDIO_STATE_CONNECTING = 1;
-    final static int AUDIO_STATE_CONNECTED = 2;
-    final static int AUDIO_STATE_CONNECTED_MSBC = 3;
+    static final int AUDIO_STATE_DISCONNECTED = 0;
+    static final int AUDIO_STATE_CONNECTING = 1;
+    static final int AUDIO_STATE_CONNECTED = 2;
+    static final int AUDIO_STATE_CONNECTED_MSBC = 3;
 
     // match up with bthf_client_vr_state_t enum of bt_hf_client.h
-    final static int VR_STATE_STOPPED = 0;
-    final static int VR_STATE_STARTED = 1;
+    static final int VR_STATE_STOPPED = 0;
+    static final int VR_STATE_STARTED = 1;
 
     // match up with bthf_client_volume_type_t enum of bt_hf_client.h
-    final static int VOLUME_TYPE_SPK = 0;
-    final static int VOLUME_TYPE_MIC = 1;
+    static final int VOLUME_TYPE_SPK = 0;
+    static final int VOLUME_TYPE_MIC = 1;
 
     // match up with bthf_client_network_state_t enum of bt_hf_client.h
-    final static int NETWORK_STATE_NOT_AVAILABLE = 0;
-    final static int NETWORK_STATE_AVAILABLE = 1;
+    static final int NETWORK_STATE_NOT_AVAILABLE = 0;
+    static final int NETWORK_STATE_AVAILABLE = 1;
 
     // match up with bthf_client_service_type_t enum of bt_hf_client.h
-    final static int SERVICE_TYPE_HOME = 0;
-    final static int SERVICE_TYPE_ROAMING = 1;
+    static final int SERVICE_TYPE_HOME = 0;
+    static final int SERVICE_TYPE_ROAMING = 1;
 
     // match up with bthf_client_call_state_t enum of bt_hf_client.h
-    final static int CALL_STATE_ACTIVE = 0;
-    final static int CALL_STATE_HELD = 1;
-    final static int CALL_STATE_DIALING = 2;
-    final static int CALL_STATE_ALERTING = 3;
-    final static int CALL_STATE_INCOMING = 4;
-    final static int CALL_STATE_WAITING = 5;
-    final static int CALL_STATE_HELD_BY_RESP_HOLD = 6;
+    static final int CALL_STATE_ACTIVE = 0;
+    static final int CALL_STATE_HELD = 1;
+    static final int CALL_STATE_DIALING = 2;
+    static final int CALL_STATE_ALERTING = 3;
+    static final int CALL_STATE_INCOMING = 4;
+    static final int CALL_STATE_WAITING = 5;
+    static final int CALL_STATE_HELD_BY_RESP_HOLD = 6;
 
     // match up with bthf_client_call_t enum of bt_hf_client.h
-    final static int CALL_NO_CALLS_IN_PROGRESS = 0;
-    final static int CALL_CALLS_IN_PROGRESS = 1;
+    static final int CALL_NO_CALLS_IN_PROGRESS = 0;
+    static final int CALL_CALLS_IN_PROGRESS = 1;
 
     // match up with bthf_client_callsetup_t enum of bt_hf_client.h
-    final static int CALLSETUP_NONE = 0;
-    final static int CALLSETUP_INCOMING = 1;
-    final static int CALLSETUP_OUTGOING = 2;
-    final static int CALLSETUP_ALERTING = 3;
+    static final int CALLSETUP_NONE = 0;
+    static final int CALLSETUP_INCOMING = 1;
+    static final int CALLSETUP_OUTGOING = 2;
+    static final int CALLSETUP_ALERTING = 3;
 
     // match up with bthf_client_callheld_t enum of bt_hf_client.h
-    final static int CALLHELD_NONE = 0;
-    final static int CALLHELD_HOLD_AND_ACTIVE = 1;
-    final static int CALLHELD_HOLD = 2;
+    static final int CALLHELD_NONE = 0;
+    static final int CALLHELD_HOLD_AND_ACTIVE = 1;
+    static final int CALLHELD_HOLD = 2;
 
     // match up with btrh_client_resp_and_hold_t of bt_hf_client.h
-    final static int RESP_AND_HOLD_HELD = 0;
-    final static int RESP_AND_HOLD_ACCEPT = 1;
-    final static int RESP_AND_HOLD_REJECT = 2;
+    static final int RESP_AND_HOLD_HELD = 0;
+    static final int RESP_AND_HOLD_ACCEPT = 1;
+    static final int RESP_AND_HOLD_REJECT = 2;
 
     // match up with bthf_client_call_direction_t enum of bt_hf_client.h
-    final static int CALL_DIRECTION_OUTGOING = 0;
-    final static int CALL_DIRECTION_INCOMING = 1;
+    static final int CALL_DIRECTION_OUTGOING = 0;
+    static final int CALL_DIRECTION_INCOMING = 1;
 
     // match up with bthf_client_call_mpty_type_t enum of bt_hf_client.h
-    final static int CALL_MPTY_TYPE_SINGLE = 0;
-    final static int CALL_MPTY_TYPE_MULTI = 1;
+    static final int CALL_MPTY_TYPE_SINGLE = 0;
+    static final int CALL_MPTY_TYPE_MULTI = 1;
 
     // match up with bthf_client_cmd_complete_t enum of bt_hf_client.h
-    final static int CMD_COMPLETE_OK = 0;
-    final static int CMD_COMPLETE_ERROR = 1;
-    final static int CMD_COMPLETE_ERROR_NO_CARRIER = 2;
-    final static int CMD_COMPLETE_ERROR_BUSY = 3;
-    final static int CMD_COMPLETE_ERROR_NO_ANSWER = 4;
-    final static int CMD_COMPLETE_ERROR_DELAYED = 5;
-    final static int CMD_COMPLETE_ERROR_BLACKLISTED = 6;
-    final static int CMD_COMPLETE_ERROR_CME = 7;
+    static final int CMD_COMPLETE_OK = 0;
+    static final int CMD_COMPLETE_ERROR = 1;
+    static final int CMD_COMPLETE_ERROR_NO_CARRIER = 2;
+    static final int CMD_COMPLETE_ERROR_BUSY = 3;
+    static final int CMD_COMPLETE_ERROR_NO_ANSWER = 4;
+    static final int CMD_COMPLETE_ERROR_DELAYED = 5;
+    static final int CMD_COMPLETE_ERROR_BLACKLISTED = 6;
+    static final int CMD_COMPLETE_ERROR_CME = 7;
 
     // match up with bthf_client_call_action_t enum of bt_hf_client.h
-    final static int CALL_ACTION_CHLD_0 = 0;
-    final static int CALL_ACTION_CHLD_1 = 1;
-    final static int CALL_ACTION_CHLD_2 = 2;
-    final static int CALL_ACTION_CHLD_3 = 3;
-    final static int CALL_ACTION_CHLD_4 = 4;
-    final static int CALL_ACTION_CHLD_1x = 5;
-    final static int CALL_ACTION_CHLD_2x = 6;
-    final static int CALL_ACTION_ATA = 7;
-    final static int CALL_ACTION_CHUP = 8;
-    final static int CALL_ACTION_BTRH_0 = 9;
-    final static int CALL_ACTION_BTRH_1 = 10;
-    final static int CALL_ACTION_BTRH_2 = 11;
+    static final int CALL_ACTION_CHLD_0 = 0;
+    static final int CALL_ACTION_CHLD_1 = 1;
+    static final int CALL_ACTION_CHLD_2 = 2;
+    static final int CALL_ACTION_CHLD_3 = 3;
+    static final int CALL_ACTION_CHLD_4 = 4;
+    static final int CALL_ACTION_CHLD_1X = 5;
+    static final int CALL_ACTION_CHLD_2X = 6;
+    static final int CALL_ACTION_ATA = 7;
+    static final int CALL_ACTION_CHUP = 8;
+    static final int CALL_ACTION_BTRH_0 = 9;
+    static final int CALL_ACTION_BTRH_1 = 10;
+    static final int CALL_ACTION_BTRH_2 = 11;
 
     // match up with bthf_client_subscriber_service_type_t enum of
     // bt_hf_client.h
-    final static int SUBSCRIBER_SERVICE_TYPE_UNKNOWN = 0;
-    final static int SUBSCRIBER_SERVICE_TYPE_VOICE = 1;
-    final static int SUBSCRIBER_SERVICE_TYPE_FAX = 2;
+    static final int SUBSCRIBER_SERVICE_TYPE_UNKNOWN = 0;
+    static final int SUBSCRIBER_SERVICE_TYPE_VOICE = 1;
+    static final int SUBSCRIBER_SERVICE_TYPE_FAX = 2;
 
     // match up with bthf_client_in_band_ring_state_t enum in bt_hf_client.h
-    final static int IN_BAND_RING_NOT_PROVIDED = 0;
-    final static int IN_BAND_RING_PROVIDED = 1;
+    static final int IN_BAND_RING_NOT_PROVIDED = 0;
+    static final int IN_BAND_RING_PROVIDED = 1;
 
     // AG features masks
     // match up with masks in bt_hf_client.h
     // Three-way calling
-    final static int PEER_FEAT_3WAY     = 0x00000001;
+    static final int PEER_FEAT_3WAY = 0x00000001;
     // Echo cancellation and/or noise reduction
-    final static int PEER_FEAT_ECNR     = 0x00000002;
+    static final int PEER_FEAT_ECNR = 0x00000002;
     // Voice recognition
-    final static int PEER_FEAT_VREC     = 0x00000004;
+    static final int PEER_FEAT_VREC = 0x00000004;
     // In-band ring tone
-    final static int PEER_FEAT_INBAND   = 0x00000008;
+    static final int PEER_FEAT_INBAND = 0x00000008;
     // Attach a phone number to a voice tag
-    final static int PEER_FEAT_VTAG     = 0x00000010;
+    static final int PEER_FEAT_VTAG = 0x00000010;
     // Ability to reject incoming call
-    final static int PEER_FEAT_REJECT   = 0x00000020;
+    static final int PEER_FEAT_REJECT = 0x00000020;
     // Enhanced Call Status
-    final static int PEER_FEAT_ECS      = 0x00000040;
+    static final int PEER_FEAT_ECS = 0x00000040;
     // Enhanced Call Control
-    final static int PEER_FEAT_ECC      = 0x00000080;
+    static final int PEER_FEAT_ECC = 0x00000080;
     // Extended error codes
-    final static int PEER_FEAT_EXTERR   = 0x00000100;
+    static final int PEER_FEAT_EXTERR = 0x00000100;
     // Codec Negotiation
-    final static int PEER_FEAT_CODEC    = 0x00000200;
+    static final int PEER_FEAT_CODEC = 0x00000200;
 
     // AG's 3WC features masks
     // match up with masks in bt_hf_client.h
     // 0  Release waiting call or held calls
-    final static int CHLD_FEAT_REL           = 0x00000001;
+    static final int CHLD_FEAT_REL = 0x00000001;
     // 1  Release active calls and accept other (waiting or held) cal
-    final static int CHLD_FEAT_REL_ACC       = 0x00000002;
+    static final int CHLD_FEAT_REL_ACC = 0x00000002;
     // 1x Release specified active call only
-    final static int CHLD_FEAT_REL_X         = 0x00000004;
+    static final int CHLD_FEAT_REL_X = 0x00000004;
     // 2  Active calls on hold and accept other (waiting or held) call
-    final static int CHLD_FEAT_HOLD_ACC      = 0x00000008;
+    static final int CHLD_FEAT_HOLD_ACC = 0x00000008;
     // 2x Request private mode with specified call (put the rest on hold)
-    final static int CHLD_FEAT_PRIV_X        = 0x00000010;
+    static final int CHLD_FEAT_PRIV_X = 0x00000010;
     // 3  Add held call to multiparty */
-    final static int CHLD_FEAT_MERGE         = 0x00000020;
+    static final int CHLD_FEAT_MERGE = 0x00000020;
     // 4  Connect two calls and leave (disconnect from) multiparty */
-    final static int CHLD_FEAT_MERGE_DETACH  = 0x00000040;
+    static final int CHLD_FEAT_MERGE_DETACH = 0x00000040;
 
     // AT Commands
     // These Commands values must match with Constants defined in
     // tBTA_HF_CLIENT_AT_CMD_TYPE in bta_hf_client_api.h
     // used for sending vendor specific AT cmds to AG.
 
-    final static int HANDSFREECLIENT_AT_CMD_NREC = 15;
+    static final int HANDSFREECLIENT_AT_CMD_NREC = 15;
 
     // Flag to check for local NREC support
-    final static boolean HANDSFREECLIENT_NREC_SUPPORTED = true;
+    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 089f4a6..46c017c 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -17,9 +17,9 @@
 package com.android.bluetooth.hfpclient;
 
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothHeadsetClientCall;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothHeadsetClient;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -28,14 +28,13 @@
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.Message;
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
-import com.android.bluetooth.Utils;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -54,8 +53,7 @@
     private static final boolean DBG = false;
     private static final String TAG = "HeadsetClientService";
 
-    private HashMap<BluetoothDevice, HeadsetClientStateMachine> mStateMachineMap =
-        new HashMap<>();
+    private HashMap<BluetoothDevice, HeadsetClientStateMachine> mStateMachineMap = new HashMap<>();
     private static HeadsetClientService sHeadsetClientService;
     private NativeInterface mNativeInterface = null;
     private HandlerThread mSmThread = null;
@@ -64,18 +62,13 @@
     // Maxinum number of devices we can try connecting to in one session
     private static final int MAX_STATE_MACHINES_POSSIBLE = 100;
 
-    public static String HFP_CLIENT_STOP_TAG = "hfp_client_stop_tag";
+    public static final String HFP_CLIENT_STOP_TAG = "hfp_client_stop_tag";
 
     static {
         NativeInterface.classInitNative();
     }
 
     @Override
-    protected String getName() {
-        return TAG;
-    }
-
-    @Override
     public IProfileServiceBinder initBinder() {
         return new BluetoothHeadsetClientBinder(this);
     }
@@ -88,17 +81,19 @@
         // Setup the JNI service
         NativeInterface.initializeNative();
         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        if (mAudioManager == null) {
+            Log.e(TAG, "AudioManager service doesn't exist?");
+        } else {
+            // start AudioManager in a known state
+            mAudioManager.setParameters("hfp_enable=false");
+        }
 
         mSmFactory = new HeadsetClientStateMachineFactory();
         mStateMachineMap.clear();
 
         IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
-        try {
-            registerReceiver(mBroadcastReceiver, filter);
-        } catch (Exception e) {
-            Log.w(TAG, "Unable to register broadcat receiver", e);
-        }
-        setHeadsetClientService(this);
+        registerReceiver(mBroadcastReceiver, filter);
+
         mNativeInterface = new NativeInterface();
 
         // Start the HfpClientConnectionService to create connection with telecom when HFP
@@ -109,23 +104,25 @@
         // Create the thread on which all State Machines will run
         mSmThread = new HandlerThread("HeadsetClient.SM");
         mSmThread.start();
-        NativeInterface.initializeNative();
 
+        setHeadsetClientService(this);
         return true;
     }
 
     @Override
     protected synchronized boolean stop() {
-        try {
-            unregisterReceiver(mBroadcastReceiver);
-        } catch (Exception e) {
-            Log.w(TAG, "Unable to unregister broadcast receiver", e);
+        if (sHeadsetClientService == null) {
+            Log.w(TAG, "stop() called without start()");
+            return false;
         }
+        setHeadsetClientService(null);
+
+        unregisterReceiver(mBroadcastReceiver);
 
         for (Iterator<Map.Entry<BluetoothDevice, HeadsetClientStateMachine>> it =
                 mStateMachineMap.entrySet().iterator(); it.hasNext(); ) {
             HeadsetClientStateMachine sm =
-                mStateMachineMap.get((BluetoothDevice) it.next().getKey());
+                    mStateMachineMap.get((BluetoothDevice) it.next().getKey());
             sm.doQuit();
             it.remove();
         }
@@ -145,13 +142,6 @@
         return true;
     }
 
-    @Override
-    protected boolean cleanup() {
-        HeadsetClientStateMachine.cleanup();
-        clearHeadsetClientService();
-        return true;
-    }
-
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -163,26 +153,25 @@
             // {@link HeadsetClientStateMachine} for details.
             if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
                 if (DBG) {
-                    Log.d(TAG,
-                            "Volume changed for stream: "
-                                    + intent.getExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE));
+                    Log.d(TAG, "Volume changed for stream: " + intent.getExtra(
+                            AudioManager.EXTRA_VOLUME_STREAM_TYPE));
                 }
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                 if (streamType == AudioManager.STREAM_VOICE_CALL) {
-                    int streamValue = intent
-                            .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+                    int streamValue =
+                            intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
                     int hfVol = HeadsetClientStateMachine.amToHfVol(streamValue);
                     if (DBG) {
                         Log.d(TAG,
-                                "Setting volume to audio manager: " + streamValue
-                                        + " hands free: " + hfVol);
+                                "Setting volume to audio manager: " + streamValue + " hands free: "
+                                        + hfVol);
                     }
                     mAudioManager.setParameters("hfp_volume=" + hfVol);
                     synchronized (this) {
                         for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
                             if (sm != null) {
-                                sm.sendMessage(
-                                        HeadsetClientStateMachine.SET_SPEAKER_VOLUME, streamValue);
+                                sm.sendMessage(HeadsetClientStateMachine.SET_SPEAKER_VOLUME,
+                                        streamValue);
                             }
                         }
                     }
@@ -198,14 +187,13 @@
             implements IProfileServiceBinder {
         private HeadsetClientService mService;
 
-        public BluetoothHeadsetClientBinder(HeadsetClientService svc) {
+        BluetoothHeadsetClientBinder(HeadsetClientService svc) {
             mService = svc;
         }
 
         @Override
-        public boolean cleanup() {
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
         private HeadsetClientService getService() {
@@ -449,50 +437,32 @@
             }
             return service.getCurrentAgFeatures(device);
         }
-    };
+    }
+
+    ;
 
     // API methods
     public static synchronized HeadsetClientService getHeadsetClientService() {
-        if (sHeadsetClientService != null && sHeadsetClientService.isAvailable()) {
-            if (DBG) {
-                Log.d(TAG, "getHeadsetClientService(): returning " + sHeadsetClientService);
-            }
-            return sHeadsetClientService;
+        if (sHeadsetClientService == null) {
+            Log.w(TAG, "getHeadsetClientService(): service is null");
+            return null;
         }
-        if (DBG) {
-            if (sHeadsetClientService == null) {
-                Log.d(TAG, "getHeadsetClientService(): service is NULL");
-            } else if (!(sHeadsetClientService.isAvailable())) {
-                Log.d(TAG, "getHeadsetClientService(): service is not available");
-            }
+        if (!sHeadsetClientService.isAvailable()) {
+            Log.w(TAG, "getHeadsetClientService(): service is not available ");
+            return null;
         }
-        return null;
+        return sHeadsetClientService;
     }
 
     private static synchronized void setHeadsetClientService(HeadsetClientService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) {
-                Log.d(TAG, "setHeadsetClientService(): set to: " + sHeadsetClientService);
-            }
-            sHeadsetClientService = instance;
-        } else {
-            if (DBG) {
-                if (sHeadsetClientService == null) {
-                    Log.d(TAG, "setHeadsetClientService(): service not available");
-                } else if (!sHeadsetClientService.isAvailable()) {
-                    Log.d(TAG, "setHeadsetClientService(): service is cleaning up");
-                }
-            }
+        if (DBG) {
+            Log.d(TAG, "setHeadsetClientService(): set to: " + instance);
         }
-    }
-
-    private static synchronized void clearHeadsetClientService() {
-        sHeadsetClientService = null;
+        sHeadsetClientService = instance;
     }
 
     public boolean connect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                "Need BLUETOOTH ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "connect " + device);
         }
@@ -512,8 +482,7 @@
     }
 
     boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                "Need BLUETOOTH ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -521,8 +490,8 @@
         }
 
         int connectionState = sm.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
-                connectionState != BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
             return false;
         }
 
@@ -567,11 +536,9 @@
     }
 
     public boolean setPriority(BluetoothDevice device, int priority) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                "Need BLUETOOTH_ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
-                priority);
+                Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), priority);
         if (DBG) {
             Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
@@ -579,8 +546,7 @@
     }
 
     public int getPriority(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                "Need BLUETOOTH_ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         int priority = Settings.Global.getInt(getContentResolver(),
                 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
                 BluetoothProfile.PRIORITY_UNDEFINED);
@@ -588,13 +554,33 @@
     }
 
     boolean startVoiceRecognition(BluetoothDevice device) {
-        Log.e(TAG, "startVoiceRecognition API not available");
-        return false;
+        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;
+        }
+        sm.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_START);
+        return true;
     }
 
     boolean stopVoiceRecognition(BluetoothDevice device) {
-        Log.e(TAG, "stopVoiceRecognition API not available");
-        return false;
+        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;
+        }
+        sm.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_STOP);
+        return true;
     }
 
     int getAudioState(BluetoothDevice device) {
@@ -649,8 +635,8 @@
         }
 
         int connectionState = sm.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
-                connectionState != BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
             return false;
         }
         Message msg = sm.obtainMessage(HeadsetClientStateMachine.HOLD_CALL);
@@ -662,15 +648,16 @@
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         /* Phonecalls from a single device are supported, hang up any calls on the other phone */
         synchronized (this) {
-            for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry :
-                    mStateMachineMap.entrySet()) {
+            for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : mStateMachineMap
+                    .entrySet()) {
                 if (entry.getValue() == null || entry.getKey().equals(device)) {
                     continue;
                 }
                 int connectionState = entry.getValue().getConnectionState(entry.getKey());
                 if (DBG) {
-                    Log.d(TAG, "Accepting a call on device " + device
-                                    + ". Possibly disconnecting on " + entry.getValue());
+                    Log.d(TAG,
+                            "Accepting a call on device " + device + ". Possibly disconnecting on "
+                                    + entry.getValue());
                 }
                 if (connectionState == BluetoothProfile.STATE_CONNECTED) {
                     entry.getValue()
@@ -704,8 +691,8 @@
         }
 
         int connectionState = sm.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
-                connectionState != BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
             return false;
         }
 
@@ -723,8 +710,8 @@
         }
 
         int connectionState = sm.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
-                connectionState != BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
             return false;
         }
 
@@ -743,8 +730,8 @@
         }
 
         int connectionState = sm.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
-                connectionState != BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
             return false;
         }
 
@@ -763,15 +750,15 @@
         }
 
         int connectionState = sm.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
-                connectionState != BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
             return null;
         }
 
-        BluetoothHeadsetClientCall call = new BluetoothHeadsetClientCall(
-            device, HeadsetClientStateMachine.HF_ORIGINATED_CALL_ID,
-            BluetoothHeadsetClientCall.CALL_STATE_DIALING, number, false  /* multiparty */,
-            true  /* outgoing */);
+        BluetoothHeadsetClientCall call = new BluetoothHeadsetClientCall(device,
+                HeadsetClientStateMachine.HF_ORIGINATED_CALL_ID,
+                BluetoothHeadsetClientCall.CALL_STATE_DIALING, number, false  /* multiparty */,
+                true  /* outgoing */, sm.getInBandRing());
         Message msg = sm.obtainMessage(HeadsetClientStateMachine.DIAL_NUMBER);
         msg.obj = call;
         sm.sendMessage(msg);
@@ -787,8 +774,8 @@
         }
 
         int connectionState = sm.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
-                connectionState != BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
             return false;
         }
         Message msg = sm.obtainMessage(HeadsetClientStateMachine.SEND_DTMF);
@@ -825,8 +812,8 @@
         }
 
         int connectionState = sm.getConnectionState(device);
-        if (connectionState != BluetoothProfile.STATE_CONNECTED &&
-                connectionState != BluetoothProfile.STATE_CONNECTING) {
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
             return false;
         }
         Message msg = sm.obtainMessage(HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER);
@@ -892,8 +879,8 @@
         // BluetoothAddresses. If it so happens instead of blowing up we can atleast put a limit on
         // how long the attack would survive
         if (mStateMachineMap.keySet().size() > MAX_STATE_MACHINES_POSSIBLE) {
-            Log.e(TAG, "Max state machines reached, possible DOS attack " +
-                MAX_STATE_MACHINES_POSSIBLE);
+            Log.e(TAG, "Max state machines reached, possible DOS attack "
+                    + MAX_STATE_MACHINES_POSSIBLE);
             return null;
         }
 
@@ -906,14 +893,14 @@
 
     // Check if any of the state machines have routed the SCO audio stream.
     synchronized boolean isScoRouted() {
-        for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry :
-                mStateMachineMap.entrySet()) {
+        for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : mStateMachineMap
+                .entrySet()) {
             if (entry.getValue() != null) {
                 int audioState = entry.getValue().getAudioState(entry.getKey());
                 if (audioState == BluetoothHeadsetClient.STATE_AUDIO_CONNECTED) {
                     if (DBG) {
                         Log.d(TAG, "Device " + entry.getKey() + " audio state " + audioState
-                                        + " Connected");
+                                + " Connected");
                     }
                     return true;
                 }
@@ -942,4 +929,8 @@
     protected void setSMFactory(HeadsetClientStateMachineFactory factory) {
         mSmFactory = factory;
     }
+
+    AudioManager getAudioManager() {
+        return mAudioManager;
+    }
 }
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
old mode 100755
new mode 100644
index 9ff30f7..844c470
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -39,20 +39,24 @@
 import android.bluetooth.BluetoothHeadsetClientCall;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
-import android.content.Context;
 import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
 import android.media.AudioManager;
 import android.os.Bundle;
-import android.os.Message;
 import android.os.Looper;
+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.telecom.TelecomManager;
 
+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.util.IState;
 import com.android.internal.util.State;
@@ -62,25 +66,26 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Hashtable;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
 import java.util.Set;
 
-import com.android.bluetooth.R;
-
 public class HeadsetClientStateMachine extends StateMachine {
     private static final String TAG = "HeadsetClientStateMachine";
     private static final boolean DBG = false;
 
     static final int NO_ACTION = 0;
+    static final int IN_BAND_RING_ENABLED = 1;
 
     // external actions
+    public static final int AT_OK = 0;
     public static final int CONNECT = 1;
     public static final int DISCONNECT = 2;
     public static final int CONNECT_AUDIO = 3;
     public static final int DISCONNECT_AUDIO = 4;
+    public static final int VOICE_RECOGNITION_START = 5;
+    public static final int VOICE_RECOGNITION_STOP = 6;
     public static final int SET_MIC_VOLUME = 7;
     public static final int SET_SPEAKER_VOLUME = 8;
     public static final int DIAL_NUMBER = 10;
@@ -103,24 +108,25 @@
     static final int TERMINATE_SPECIFIC_CALL = 53;
 
     // Timeouts.
+    @VisibleForTesting
     static final int CONNECTING_TIMEOUT_MS = 10000;  // 10s
-    static final int ROUTING_DELAY_MS = 250;
-    static final int SCO_DISCONNECT_TIMEOUT_MS = 750;
+    private static final int ROUTING_DELAY_MS = 250;
 
-    static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec.
-    static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec.
+    private static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec.
+    private static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec.
 
-    public static final Integer HF_ORIGINATED_CALL_ID = new Integer(-1);
-    private long OUTGOING_TIMEOUT_MILLI = 10 * 1000; // 10 seconds
-    private long QUERY_CURRENT_CALLS_WAIT_MILLIS = 2 * 1000; // 2 seconds
+    static final int HF_ORIGINATED_CALL_ID = -1;
+    private static final long OUTGOING_TIMEOUT_MILLI = 10 * 1000; // 10 seconds
+    private static final long QUERY_CURRENT_CALLS_WAIT_MILLIS = 2 * 1000; // 2 seconds
 
     // Keep track of audio routing across all devices.
-    private static boolean sAudioIsRouted = true;
+    private static boolean sAudioIsRouted = false;
 
     private final Disconnected mDisconnected;
     private final Connecting mConnecting;
     private final Connected mConnected;
     private final AudioOn mAudioOn;
+    private State mPrevState;
     private long mClccTimer = 0;
 
     private final HeadsetClientService mService;
@@ -136,12 +142,13 @@
     private int mIndicatorNetworkType;
     private int mIndicatorNetworkSignal;
     private int mIndicatorBatteryLevel;
+    private boolean mInBandRing;
 
     private String mOperatorName;
     private String mSubscriberInfo;
 
-    private static int mMaxAmVcVol;
-    private static int mMinAmVcVol;
+    private static int sMaxAmVcVol;
+    private static int sMinAmVcVol;
 
     // queue of send actions (pair action, action_data)
     private Queue<Pair<Integer, Object>> mQueuedActions;
@@ -150,11 +157,10 @@
     // indicator
     private Pair<Integer, Object> mPendingAction;
 
-    private static AudioManager sAudioManager;
     private int mAudioState;
     private boolean mAudioWbs;
+    private int mVoiceRecognitionActive;
     private final BluetoothAdapter mAdapter;
-    private TelecomManager mTelecomManager;
 
     // currently connected device
     private BluetoothDevice mCurrentDevice = null;
@@ -163,11 +169,21 @@
     private int mPeerFeatures;
     private int mChldFeatures;
 
+    // This is returned when requesting focus from AudioManager
+    private AudioFocusRequest mAudioFocusRequest;
+
+    private AudioManager mAudioManager;
+
     // Accessor for the states, useful for reusing the state machines
     public IState getDisconnectedState() {
         return mDisconnected;
     }
 
+    // Get if in band ring is currently enabled on device.
+    public boolean getInBandRing() {
+        return mInBandRing;
+    }
+
     public void dump(StringBuilder sb) {
         ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice);
         ProfileService.println(sb, "mAudioState: " + mAudioState);
@@ -262,8 +278,6 @@
         if (DBG) {
             Log.d(TAG, "queryCallsDone");
         }
-        Iterator<Hashtable.Entry<Integer, BluetoothHeadsetClientCall>> it;
-
         // 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
@@ -311,9 +325,9 @@
         callRetainedIds.retainAll(newCallIdSet);
 
         if (DBG) {
-            Log.d(TAG, "currCallIdSet " + mCalls.keySet() + " newCallIdSet " + newCallIdSet +
-                " callAddedIds " + callAddedIds + " callRemovedIds " + callRemovedIds +
-                " callRetainedIds " + callRetainedIds);
+            Log.d(TAG, "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.
@@ -353,9 +367,9 @@
         }
 
         if (DBG) {
-            Log.d(TAG, "ADJUST: currCallIdSet " + mCalls.keySet() + " newCallIdSet " +
-                newCallIdSet + " callAddedIds " + callAddedIds + " callRemovedIds " +
-                callRemovedIds + " callRetainedIds " + callRetainedIds);
+            Log.d(TAG, "ADJUST: currCallIdSet " + mCalls.keySet() + " newCallIdSet " + newCallIdSet
+                    + " callAddedIds " + callAddedIds + " callRemovedIds " + callRemovedIds
+                    + " callRetainedIds " + callRetainedIds);
         }
 
         // Terminate & remove the calls that are done.
@@ -387,7 +401,16 @@
         }
 
         if (mCalls.size() > 0) {
-            sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
+            if (mService.getResources().getBoolean(R.bool.hfp_clcc_poll_during_call)) {
+                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");
+                    sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
+                } else {
+                    removeMessages(QUERY_CURRENT_CALLS);
+                }
+            }
         }
 
         mCallsUpdate.clear();
@@ -398,8 +421,9 @@
         if (DBG) {
             Log.d(TAG, "queryCallsUpdate: " + id);
         }
-        mCallsUpdate.put(id, new BluetoothHeadsetClientCall(
-            mCurrentDevice, id, state, number, multiParty, outgoing));
+        mCallsUpdate.put(id,
+                new BluetoothHeadsetClientCall(mCurrentDevice, id, state, number, multiParty,
+                        outgoing, mInBandRing));
     }
 
     private void acceptCall(int flag) {
@@ -442,8 +466,8 @@
 
                 // if active calls are present then we have the option to either terminate the
                 // 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 (flag == BluetoothHeadsetClient.CALL_ACCEPT_HOLD
+                        || flag == BluetoothHeadsetClient.CALL_ACCEPT_NONE) {
                     if (DBG) {
                         Log.d(TAG, "Accepting call with accept and hold");
                     }
@@ -500,8 +524,7 @@
             Log.d(TAG, "rejectCall");
         }
 
-        BluetoothHeadsetClientCall c =
-                getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
+        BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_INCOMING,
                 BluetoothHeadsetClientCall.CALL_STATE_WAITING,
                 BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD,
                 BluetoothHeadsetClientCall.CALL_STATE_HELD);
@@ -573,10 +596,14 @@
 
         int action = HeadsetClientHalConstants.CALL_ACTION_CHUP;
 
-        BluetoothHeadsetClientCall c = getCall(
-                BluetoothHeadsetClientCall.CALL_STATE_DIALING,
+        BluetoothHeadsetClientCall c = getCall(BluetoothHeadsetClientCall.CALL_STATE_DIALING,
                 BluetoothHeadsetClientCall.CALL_STATE_ALERTING,
                 BluetoothHeadsetClientCall.CALL_STATE_ACTIVE);
+        if (c == null) {
+            // If the call being terminated is currently held, switch the action to CHLD_0
+            c = getCall(BluetoothHeadsetClientCall.CALL_STATE_HELD);
+            action = HeadsetClientHalConstants.CALL_ACTION_CHLD_0;
+        }
         if (c != null) {
             if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice), action, 0)) {
                 addQueuedAction(TERMINATE_CALL, action);
@@ -593,12 +620,13 @@
 
         BluetoothHeadsetClientCall c = mCalls.get(idx);
 
-        if (c == null ||
-            c.getState() != BluetoothHeadsetClientCall.CALL_STATE_ACTIVE ||
-            !c.isMultiParty()) return;
+        if (c == null || c.getState() != BluetoothHeadsetClientCall.CALL_STATE_ACTIVE
+                || !c.isMultiParty()) {
+            return;
+        }
 
         if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice),
-                HeadsetClientHalConstants.CALL_ACTION_CHLD_2x, idx)) {
+                HeadsetClientHalConstants.CALL_ACTION_CHLD_2X, idx)) {
             addQueuedAction(ENTER_PRIVATE_MODE, c);
         } else {
             Log.e(TAG, "ERROR: Couldn't enter private " + " id:" + idx);
@@ -616,7 +644,7 @@
         }
 
         if (NativeInterface.handleCallActionNative(getByteAddress(mCurrentDevice),
-              HeadsetClientHalConstants.CALL_ACTION_CHLD_4, -1)) {
+                HeadsetClientHalConstants.CALL_ACTION_CHLD_4, -1)) {
             addQueuedAction(EXPLICIT_CALL_TRANSFER);
         } else {
             Log.e(TAG, "ERROR: Couldn't transfer call");
@@ -625,66 +653,62 @@
 
     public Bundle getCurrentAgFeatures() {
         Bundle b = new Bundle();
-        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY) ==
-                HeadsetClientHalConstants.PEER_FEAT_3WAY) {
+        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY)
+                == HeadsetClientHalConstants.PEER_FEAT_3WAY) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
         }
-        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT) ==
-                HeadsetClientHalConstants.PEER_FEAT_REJECT) {
+        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
+                == HeadsetClientHalConstants.PEER_FEAT_REJECT) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
         }
-        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC) ==
-                HeadsetClientHalConstants.PEER_FEAT_ECC) {
+        if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC)
+                == HeadsetClientHalConstants.PEER_FEAT_ECC) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, true);
         }
 
         // add individual CHLD support extras
-        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) ==
-                HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {
+        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC)
+                == HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL, true);
         }
-        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL) ==
-                HeadsetClientHalConstants.CHLD_FEAT_REL) {
-            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL, true);
+        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL)
+                == HeadsetClientHalConstants.CHLD_FEAT_REL) {
+            b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL,
+                    true);
         }
-        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) ==
-                HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {
+        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC)
+                == HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT, true);
         }
-        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE) ==
-                HeadsetClientHalConstants.CHLD_FEAT_MERGE) {
+        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE)
+                == HeadsetClientHalConstants.CHLD_FEAT_MERGE) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE, true);
         }
-        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) ==
-                HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
+        if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH)
+                == HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
             b.putBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
         }
 
         return b;
     }
 
-    protected HeadsetClientStateMachine(HeadsetClientService context, Looper looper) {
+    HeadsetClientStateMachine(HeadsetClientService context, Looper looper) {
         super(TAG, looper);
         mService = context;
+        mAudioManager = mService.getAudioManager();
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        if (sAudioManager == null) {
-            sAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-            // Initialize hfp_enable into a known state.
-            routeHfpAudio(false);
-        }
         mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
         mAudioWbs = false;
-
-        mTelecomManager = (TelecomManager) context.getSystemService(context.TELECOM_SERVICE);
+        mVoiceRecognitionActive = HeadsetClientHalConstants.VR_STATE_STOPPED;
 
         mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
         mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
         mIndicatorNetworkSignal = 0;
         mIndicatorBatteryLevel = 0;
 
-        mMaxAmVcVol = sAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
-        mMinAmVcVol = sAudioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL);
+        sMaxAmVcVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
+        sMinAmVcVol = mAudioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL);
 
         mOperatorName = null;
         mSubscriberInfo = null;
@@ -717,43 +741,66 @@
         return hfcsm;
     }
 
-    static synchronized void routeHfpAudio(boolean enable) {
+    synchronized void routeHfpAudio(boolean enable) {
+        if (mAudioManager == null) {
+            Log.e(TAG, "AudioManager is null!");
+            return;
+        }
         if (DBG) {
             Log.d(TAG, "hfp_enable=" + enable);
         }
         if (enable && !sAudioIsRouted) {
-            sAudioManager.setParameters("hfp_enable=true");
+            mAudioManager.setParameters("hfp_enable=true");
         } else if (!enable) {
-            sAudioManager.setParameters("hfp_enable=false");
+            mAudioManager.setParameters("hfp_enable=false");
         }
         sAudioIsRouted = enable;
     }
 
+    private AudioFocusRequest requestAudioFocus() {
+        AudioAttributes streamAttributes =
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+                        .build();
+        AudioFocusRequest focusRequest =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+                        .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);
+        }
+        return focusRequest;
+    }
+
     public void doQuit() {
         Log.d(TAG, "doQuit");
-        if (sAudioManager != null) {
-            routeHfpAudio(false);
-        }
+        routeHfpAudio(false);
+        returnAudioFocusIfNecessary();
         quitNow();
     }
 
-    public static void cleanup() {
+    private void returnAudioFocusIfNecessary() {
+        if (mAudioFocusRequest == null) return;
+        mAudioManager.abandonAudioFocusRequest(mAudioFocusRequest);
+        mAudioFocusRequest = null;
     }
 
     static int hfToAmVol(int hfVol) {
-        int amRange = mMaxAmVcVol - mMinAmVcVol;
+        int amRange = sMaxAmVcVol - sMinAmVcVol;
         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 = mMinAmVcVol + amOffset;
+        int amOffset = (amRange * (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME)) / hfRange;
+        int amVol = sMinAmVcVol + amOffset;
         Log.d(TAG, "HF -> AM " + hfVol + " " + amVol);
         return amVol;
     }
 
     static int amToHfVol(int amVol) {
-        int amRange = mMaxAmVcVol - mMinAmVcVol;
+        int amRange = (sMaxAmVcVol > sMinAmVcVol) ? (sMaxAmVcVol - sMinAmVcVol) : 1;
         int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
-        int hfOffset = (hfRange * (amVol - mMinAmVcVol)) / amRange;
+        int hfOffset = (hfRange * (amVol - sMinAmVcVol)) / amRange;
         int hfVol = MIN_HFP_SCO_VOICE_CALL_VOLUME + hfOffset;
         Log.d(TAG, "AM -> HF " + amVol + " " + hfVol);
         return hfVol;
@@ -769,6 +816,7 @@
             mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
             mIndicatorNetworkSignal = 0;
             mIndicatorBatteryLevel = 0;
+            mInBandRing = false;
 
             mAudioWbs = false;
 
@@ -780,9 +828,6 @@
             mQueuedActions = new LinkedList<Pair<Integer, Object>>();
             clearPendingAction();
 
-
-            mCurrentDevice = null;
-
             mCalls.clear();
             mCallsUpdate.clear();
 
@@ -790,6 +835,18 @@
             mChldFeatures = 0;
 
             removeMessages(QUERY_CURRENT_CALLS);
+
+            if (mPrevState == mConnecting) {
+                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
+                        BluetoothProfile.STATE_CONNECTING);
+            } else if (mPrevState == mConnected || mPrevState == mAudioOn) {
+                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
+                        BluetoothProfile.STATE_CONNECTED);
+            } else if (mPrevState != null) { // null is the default state before Disconnected
+                Log.e(TAG, "Connected: Illegal state transition from " + mPrevState.getName()
+                        + " to Connecting, mCurrentDevice=" + mCurrentDevice);
+            }
+            mCurrentDevice = null;
         }
 
         @Override
@@ -804,17 +861,13 @@
             switch (message.what) {
                 case CONNECT:
                     BluetoothDevice device = (BluetoothDevice) message.obj;
-                    broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                            BluetoothProfile.STATE_DISCONNECTED);
-
                     if (!NativeInterface.connectNative(getByteAddress(device))) {
+                        // No state transition is involved, fire broadcast immediately
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTING);
+                                BluetoothProfile.STATE_DISCONNECTED);
                         break;
                     }
-
                     mCurrentDevice = device;
-
                     transitionTo(mConnecting);
                     break;
                 case DISCONNECT:
@@ -845,26 +898,23 @@
         }
 
         // in Disconnected state
-        private void processConnectionEvent(int state, BluetoothDevice device)
-        {
+        private void processConnectionEvent(int state, BluetoothDevice device) {
             switch (state) {
                 case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
                     Log.w(TAG, "HFPClient Connecting from Disconnected state");
                     if (okToConnect(device)) {
                         Log.i(TAG, "Incoming AG accepted");
-                        broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
-                                BluetoothProfile.STATE_DISCONNECTED);
                         mCurrentDevice = device;
                         transitionTo(mConnecting);
                     } else {
-                        Log.i(TAG, "Incoming AG rejected. priority=" +
-                            mService.getPriority(device) +
-                            " bondState=" + device.getBondState());
+                        Log.i(TAG, "Incoming AG rejected. priority=" + mService.getPriority(device)
+                                + " bondState=" + device.getBondState());
                         // reject the connection and stay in Disconnected state
                         // itself
                         NativeInterface.disconnectNative(getByteAddress(device));
                         // the other profile connection should be initiated
                         AdapterService adapterService = AdapterService.getAdapterService();
+                        // No state transition is involved, fire broadcast immediately
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
                                 BluetoothProfile.STATE_DISCONNECTED);
                     }
@@ -883,6 +933,7 @@
             if (DBG) {
                 Log.d(TAG, "Exit Disconnected: " + getCurrentMessage().what);
             }
+            mPrevState = this;
         }
     }
 
@@ -896,6 +947,14 @@
             // removed in exit. It is safe to send a CONNECTING_TIMEOUT here since
             // the only transition is when connection attempt is initiated.
             sendMessageDelayed(CONNECTING_TIMEOUT, CONNECTING_TIMEOUT_MS);
+            if (mPrevState == mDisconnected) {
+                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTING,
+                        BluetoothProfile.STATE_DISCONNECTED);
+            } else {
+                String prevStateName = mPrevState == null ? "null" : mPrevState.getName();
+                Log.e(TAG, "Connected: Illegal state transition from " + prevStateName
+                        + " to Connecting, mCurrentDevice=" + mCurrentDevice);
+            }
         }
 
         @Override
@@ -918,11 +977,12 @@
                     switch (event.type) {
                         case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
                             if (DBG) {
-                                Log.d(TAG, "Connecting: Connection " + event.device + " state changed:"
-                                        + event.valueInt);
+                                Log.d(TAG,
+                                        "Connecting: Connection " + event.device + " state changed:"
+                                                + event.valueInt);
                             }
-                            processConnectionEvent(event.valueInt, event.valueInt2,
-                                    event.valueInt3, event.device);
+                            processConnectionEvent(event.valueInt, event.valueInt2, event.valueInt3,
+                                    event.device);
                             break;
                         case StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
                         case StackEvent.EVENT_TYPE_NETWORK_STATE:
@@ -948,14 +1008,10 @@
                     }
                     break;
                 case CONNECTING_TIMEOUT:
-                      // We timed out trying to connect, transition to disconnected.
-                      Log.w(TAG, "Connection timeout for " + mCurrentDevice);
-                      transitionTo(mDisconnected);
-                      broadcastConnectionState(
-                          mCurrentDevice,
-                          BluetoothProfile.STATE_DISCONNECTED,
-                          BluetoothProfile.STATE_CONNECTING);
-                      break;
+                    // We timed out trying to connect, transition to disconnected.
+                    Log.w(TAG, "Connection timeout for " + mCurrentDevice);
+                    transitionTo(mDisconnected);
+                    break;
 
                 default:
                     Log.w(TAG, "Message not handled " + message);
@@ -965,20 +1021,18 @@
         }
 
         // in Connecting state
-        private void processConnectionEvent(
-                int state, int peer_feat, int chld_feat, BluetoothDevice device) {
+        private void processConnectionEvent(int state, int peerFeat, int chldFeat,
+                BluetoothDevice device) {
             switch (state) {
                 case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
-                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_DISCONNECTED,
-                            BluetoothProfile.STATE_CONNECTING);
                     transitionTo(mDisconnected);
                     break;
 
                 case HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED:
                     Log.d(TAG, "HFPClient Connected from Connecting state");
 
-                    mPeerFeatures = peer_feat;
-                    mChldFeatures = chld_feat;
+                    mPeerFeatures = peerFeat;
+                    mChldFeatures = chldFeat;
 
                     // We do not support devices which do not support enhanced call status (ECS).
                     if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECS) == 0) {
@@ -986,39 +1040,35 @@
                         return;
                     }
 
-                    broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
-                        BluetoothProfile.STATE_CONNECTING);
-
                     // Send AT+NREC to remote if supported by audio
-                    if (HeadsetClientHalConstants.HANDSFREECLIENT_NREC_SUPPORTED &&
-                            ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECNR) ==
-                                    HeadsetClientHalConstants.PEER_FEAT_ECNR)) {
+                    if (HeadsetClientHalConstants.HANDSFREECLIENT_NREC_SUPPORTED && (
+                            (mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECNR)
+                                    == HeadsetClientHalConstants.PEER_FEAT_ECNR)) {
                         if (NativeInterface.sendATCmdNative(getByteAddress(mCurrentDevice),
-                              HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_NREC,
-                              1 , 0, null)) {
+                                HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_NREC, 1, 0,
+                                null)) {
                             addQueuedAction(DISABLE_NREC);
                         } else {
                             Log.e(TAG, "Failed to send NREC");
                         }
                     }
-                    transitionTo(mConnected);
 
-                    int amVol = sAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
-                    sendMessage(
+                    int amVol = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
+                    deferMessage(
                             obtainMessage(HeadsetClientStateMachine.SET_SPEAKER_VOLUME, amVol, 0));
                     // Mic is either in ON state (full volume) or OFF state. There is no way in
                     // Android to change the MIC volume.
-                    sendMessage(obtainMessage(HeadsetClientStateMachine.SET_MIC_VOLUME,
-                            sAudioManager.isMicrophoneMute() ? 0 : 15, 0));
-
+                    deferMessage(obtainMessage(HeadsetClientStateMachine.SET_MIC_VOLUME,
+                            mAudioManager.isMicrophoneMute() ? 0 : 15, 0));
                     // query subscriber info
-                    sendMessage(HeadsetClientStateMachine.SUBSCRIBER_INFO);
+                    deferMessage(obtainMessage(HeadsetClientStateMachine.SUBSCRIBER_INFO));
+                    transitionTo(mConnected);
                     break;
 
                 case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
                     if (!mCurrentDevice.equals(device)) {
                         Log.w(TAG, "incoming connection event, device: " + device);
-
+                        // No state transition is involved, fire broadcast immediately
                         broadcastConnectionState(mCurrentDevice,
                                 BluetoothProfile.STATE_DISCONNECTED,
                                 BluetoothProfile.STATE_CONNECTING);
@@ -1047,6 +1097,7 @@
                 Log.d(TAG, "Exit Connecting: " + getCurrentMessage().what);
             }
             removeMessages(CONNECTING_TIMEOUT);
+            mPrevState = this;
         }
     }
 
@@ -1060,6 +1111,16 @@
             }
             mAudioWbs = false;
             mCommandedSpeakerVolume = -1;
+            if (mPrevState == mConnecting) {
+                broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
+                        BluetoothProfile.STATE_CONNECTING);
+                MetricsLogger.logProfileConnectionEvent(
+                        BluetoothMetricsProto.ProfileId.HEADSET_CLIENT);
+            } else if (mPrevState != mAudioOn) {
+                String prevStateName = mPrevState == null ? "null" : mPrevState.getName();
+                Log.e(TAG, "Connected: Illegal state transition from " + prevStateName
+                        + " to Connecting, mCurrentDevice=" + mCurrentDevice);
+            }
         }
 
         @Override
@@ -1081,7 +1142,6 @@
                         // already connected to this device, do nothing
                         break;
                     }
-
                     NativeInterface.connectNative(getByteAddress(device));
                     break;
                 case DISCONNECT:
@@ -1089,19 +1149,15 @@
                     if (!mCurrentDevice.equals(dev)) {
                         break;
                     }
-                    broadcastConnectionState(dev, BluetoothProfile.STATE_DISCONNECTING,
-                            BluetoothProfile.STATE_CONNECTED);
                     if (!NativeInterface.disconnectNative(getByteAddress(dev))) {
-                        // disconnecting failed
-                        broadcastConnectionState(dev, BluetoothProfile.STATE_CONNECTED,
-                                BluetoothProfile.STATE_DISCONNECTING);
-                        break;
+                        Log.e(TAG, "disconnectNative failed for " + dev);
                     }
                     break;
 
                 case CONNECT_AUDIO:
                     if (!NativeInterface.connectAudioNative(getByteAddress(mCurrentDevice))) {
                         Log.e(TAG, "ERROR: Couldn't connect Audio for device " + mCurrentDevice);
+                        // No state transition is involved, fire broadcast immediately
                         broadcastAudioState(mCurrentDevice,
                                 BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
                                 BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
@@ -1116,6 +1172,28 @@
                     }
                     break;
 
+                case VOICE_RECOGNITION_START:
+                    if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STOPPED) {
+                        if (NativeInterface.startVoiceRecognitionNative(
+                                    getByteAddress(mCurrentDevice))) {
+                            addQueuedAction(VOICE_RECOGNITION_START);
+                        } else {
+                            Log.e(TAG, "ERROR: Couldn't start voice recognition");
+                        }
+                    }
+                    break;
+
+                case VOICE_RECOGNITION_STOP:
+                    if (mVoiceRecognitionActive == HeadsetClientHalConstants.VR_STATE_STARTED) {
+                        if (NativeInterface.stopVoiceRecognitionNative(
+                                    getByteAddress(mCurrentDevice))) {
+                            addQueuedAction(VOICE_RECOGNITION_STOP);
+                        } else {
+                            Log.e(TAG, "ERROR: Couldn't stop voice recognition");
+                        }
+                    }
+                    break;
+
                 // Called only for Mute/Un-mute - Mic volume change is not allowed.
                 case SET_MIC_VOLUME:
                     break;
@@ -1128,7 +1206,7 @@
                         // Volume was changed by a 3rd party
                         mCommandedSpeakerVolume = -1;
                         if (NativeInterface.setVolumeNative(getByteAddress(mCurrentDevice),
-                                    HeadsetClientHalConstants.VOLUME_TYPE_SPK, hfVol)) {
+                                HeadsetClientHalConstants.VOLUME_TYPE_SPK, hfVol)) {
                             addQueuedAction(SET_SPEAKER_VOLUME);
                         }
                     }
@@ -1143,7 +1221,8 @@
                         // Start looping on calling current calls.
                         sendMessage(QUERY_CURRENT_CALLS);
                     } else {
-                        Log.e(TAG, "ERROR: Cannot dial with a given number:" + (String) message.obj);
+                        Log.e(TAG,
+                                "ERROR: Cannot dial with a given number:" + (String) message.obj);
                         // Set the call to terminated remove.
                         c.setState(BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
                         sendCallChangedIntent(c);
@@ -1169,33 +1248,28 @@
                     explicitCallTransfer();
                     break;
                 case SEND_DTMF:
-                    if (NativeInterface.sendDtmfNative(getByteAddress(mCurrentDevice), (byte) message.arg1)) {
+                    if (NativeInterface.sendDtmfNative(getByteAddress(mCurrentDevice),
+                            (byte) message.arg1)) {
                         addQueuedAction(SEND_DTMF);
                     } else {
                         Log.e(TAG, "ERROR: Couldn't send DTMF");
                     }
                     break;
                 case SUBSCRIBER_INFO:
-                    if (NativeInterface.retrieveSubscriberInfoNative(getByteAddress(mCurrentDevice))) {
+                    if (NativeInterface.retrieveSubscriberInfoNative(
+                            getByteAddress(mCurrentDevice))) {
                         addQueuedAction(SUBSCRIBER_INFO);
                     } else {
                         Log.e(TAG, "ERROR: Couldn't retrieve subscriber info");
                     }
                     break;
                 case QUERY_CURRENT_CALLS:
-                    // Whenever the timer expires we query calls if there are outstanding requests
-                    // for query calls.
-                    long currentElapsed = SystemClock.elapsedRealtime();
-                    if (mClccTimer < currentElapsed) {
-                        queryCallsStart();
-                        mClccTimer = currentElapsed + QUERY_CURRENT_CALLS_WAIT_MILLIS;
-                        // Request satisfied, ignore all other call query messages.
-                        removeMessages(QUERY_CURRENT_CALLS);
-                    } else {
-                        // Replace all messages with one concrete message.
-                        removeMessages(QUERY_CURRENT_CALLS);
+                    removeMessages(QUERY_CURRENT_CALLS);
+                    if (mCalls.size() > 0) {
+                        // If there are ongoing calls periodically check their status.
                         sendMessageDelayed(QUERY_CURRENT_CALLS, QUERY_CURRENT_CALLS_WAIT_MILLIS);
                     }
+                    queryCallsStart();
                     break;
                 case StackEvent.STACK_EVENT:
                     Intent intent = null;
@@ -1210,16 +1284,14 @@
                                 Log.d(TAG, "Connected: Connection state changed: " + event.device
                                         + ": " + event.valueInt);
                             }
-                            processConnectionEvent(
-                                event.valueInt, event.device);
+                            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);
                             }
-                            processAudioEvent(
-                                event.valueInt, event.device);
+                            processAudioEvent(event.valueInt, event.device);
                             break;
                         case StackEvent.EVENT_TYPE_NETWORK_STATE:
                             if (DBG) {
@@ -1231,19 +1303,18 @@
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS,
                                     event.valueInt);
 
-                            if (mIndicatorNetworkState ==
-                                    HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
+                            if (mIndicatorNetworkState
+                                    == HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
                                 mOperatorName = null;
                                 intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME,
                                         mOperatorName);
                             }
 
-                            intent.putExtra(
-                                BluetoothDevice.EXTRA_DEVICE, event.device);
+                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
                             mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
 
-                            if (mIndicatorNetworkState ==
-                                    HeadsetClientHalConstants.NETWORK_STATE_AVAILABLE) {
+                            if (mIndicatorNetworkState
+                                    == HeadsetClientHalConstants.NETWORK_STATE_AVAILABLE) {
                                 if (NativeInterface.queryCurrentOperatorNameNative(
                                         getByteAddress(mCurrentDevice))) {
                                     addQueuedAction(QUERY_OPERATOR_NAME);
@@ -1258,8 +1329,7 @@
                             intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING,
                                     event.valueInt);
-                            intent.putExtra(
-                                BluetoothDevice.EXTRA_DEVICE, event.device);
+                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
                             mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
                             break;
                         case StackEvent.EVENT_TYPE_NETWORK_SIGNAL:
@@ -1268,8 +1338,7 @@
                             intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH,
                                     event.valueInt);
-                            intent.putExtra(
-                                BluetoothDevice.EXTRA_DEVICE, event.device);
+                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
                             mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
                             break;
                         case StackEvent.EVENT_TYPE_BATTERY_LEVEL:
@@ -1278,8 +1347,7 @@
                             intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
                                     event.valueInt);
-                            intent.putExtra(
-                                BluetoothDevice.EXTRA_DEVICE, event.device);
+                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
                             mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
                             break;
                         case StackEvent.EVENT_TYPE_OPERATOR_NAME:
@@ -1288,10 +1356,20 @@
                             intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME,
                                     event.valueString);
-                            intent.putExtra(
-                                BluetoothDevice.EXTRA_DEVICE, event.device);
+                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
                             mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
                             break;
+                        case StackEvent.EVENT_TYPE_VR_STATE_CHANGED:
+                            if (mVoiceRecognitionActive != event.valueInt) {
+                                mVoiceRecognitionActive = event.valueInt;
+
+                                intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+                                intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION,
+                                        mVoiceRecognitionActive);
+                                intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
+                                mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                            }
+                            break;
                         case StackEvent.EVENT_TYPE_CALL:
                         case StackEvent.EVENT_TYPE_CALLSETUP:
                         case StackEvent.EVENT_TYPE_CALLHELD:
@@ -1301,24 +1379,21 @@
                             sendMessage(QUERY_CURRENT_CALLS);
                             break;
                         case StackEvent.EVENT_TYPE_CURRENT_CALLS:
-                            queryCallsUpdate(
-                                    event.valueInt,
-                                    event.valueInt3,
-                                    event.valueString,
-                                    event.valueInt4 ==
-                                            HeadsetClientHalConstants.CALL_MPTY_TYPE_MULTI,
-                                    event.valueInt2 ==
-                                            HeadsetClientHalConstants.CALL_DIRECTION_OUTGOING);
+                            queryCallsUpdate(event.valueInt, event.valueInt3, event.valueString,
+                                    event.valueInt4
+                                            == HeadsetClientHalConstants.CALL_MPTY_TYPE_MULTI,
+                                    event.valueInt2
+                                            == HeadsetClientHalConstants.CALL_DIRECTION_OUTGOING);
                             break;
                         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);
-                                sAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                                mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
                                         +mCommandedSpeakerVolume, AudioManager.FLAG_SHOW_UI);
                             } else if (event.valueInt
                                     == HeadsetClientHalConstants.VOLUME_TYPE_MIC) {
-                                sAudioManager.setMicrophoneMute(event.valueInt2 == 0);
+                                mAudioManager.setMicrophoneMute(event.valueInt2 == 0);
                             }
                             break;
                         case StackEvent.EVENT_TYPE_CMD_RESULT:
@@ -1339,6 +1414,18 @@
                                 case QUERY_CURRENT_CALLS:
                                     queryCallsDone();
                                     break;
+                                case VOICE_RECOGNITION_START:
+                                    if (event.valueInt == AT_OK) {
+                                        mVoiceRecognitionActive =
+                                                HeadsetClientHalConstants.VR_STATE_STARTED;
+                                    }
+                                    break;
+                                case VOICE_RECOGNITION_STOP:
+                                    if (event.valueInt == AT_OK) {
+                                        mVoiceRecognitionActive =
+                                                HeadsetClientHalConstants.VR_STATE_STOPPED;
+                                    }
+                                    break;
                                 default:
                                     Log.w(TAG, "Unhandled AT OK " + event);
                                     break;
@@ -1350,10 +1437,21 @@
                             intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO,
                                     mSubscriberInfo);
-                            intent.putExtra(
-                                BluetoothDevice.EXTRA_DEVICE, event.device);
+                            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
                             mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
                             break;
+                        case StackEvent.EVENT_TYPE_IN_BAND_RINGTONE:
+                            intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+                            mInBandRing = event.valueInt == IN_BAND_RING_ENABLED;
+                            intent.putExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
+                                    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);
+                            }
+                            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
@@ -1380,9 +1478,6 @@
                     }
                     // AG disconnects
                     if (mCurrentDevice.equals(device)) {
-                        broadcastConnectionState(mCurrentDevice,
-                                BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTED);
                         transitionTo(mDisconnected);
                     } else {
                         Log.e(TAG, "Disconnected from unknown device: " + device);
@@ -1428,41 +1523,43 @@
 
                     // We need to set the volume after switching into HFP mode as some Audio HALs
                     // reset the volume to a known-default on mode switch.
-                    final int amVol = sAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
+                    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);
+                        Log.d(TAG, "hfp_enable=true mAudioWbs is " + mAudioWbs);
                     }
                     if (mAudioWbs) {
                         if (DBG) {
-                            Log.d(TAG,"Setting sampling rate as 16000");
+                            Log.d(TAG, "Setting sampling rate as 16000");
                         }
-                        sAudioManager.setParameters("hfp_set_sampling_rate=16000");
-                    }
-                    else {
+                        mAudioManager.setParameters("hfp_set_sampling_rate=16000");
+                    } else {
                         if (DBG) {
-                            Log.d(TAG,"Setting sampling rate as 8000");
+                            Log.d(TAG, "Setting sampling rate as 8000");
                         }
-                        sAudioManager.setParameters("hfp_set_sampling_rate=8000");
+                        mAudioManager.setParameters("hfp_set_sampling_rate=8000");
                     }
                     if (DBG) {
                         Log.d(TAG, "hf_volume " + hfVol);
                     }
                     routeHfpAudio(true);
-                    sAudioManager.setParameters("hfp_volume=" + hfVol);
+                    mAudioFocusRequest = requestAudioFocus();
+                    mAudioManager.setParameters("hfp_volume=" + hfVol);
                     transitionTo(mAudioOn);
                     break;
 
                 case HeadsetClientHalConstants.AUDIO_STATE_CONNECTING:
-                    broadcastAudioState(
-                            device, BluetoothHeadsetClient.STATE_AUDIO_CONNECTING, mAudioState);
+                    // No state transition is involved, fire broadcast immediately
+                    broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_CONNECTING,
+                            mAudioState);
                     mAudioState = BluetoothHeadsetClient.STATE_AUDIO_CONNECTING;
                     break;
 
                 case HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED:
-                    broadcastAudioState(
-                            device, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED, mAudioState);
+                    // No state transition is involved, fire broadcast immediately
+                    broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
+                            mAudioState);
                     mAudioState = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
                     break;
 
@@ -1477,6 +1574,7 @@
             if (DBG) {
                 Log.d(TAG, "Exit Connected: " + getCurrentMessage().what);
             }
+            mPrevState = this;
         }
     }
 
@@ -1487,7 +1585,7 @@
                 Log.d(TAG, "Enter AudioOn: " + getCurrentMessage().what);
             }
             broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_CONNECTED,
-                BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
+                    BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
         }
 
         @Override
@@ -1521,6 +1619,7 @@
                      */
                     if (NativeInterface.disconnectAudioNative(getByteAddress(mCurrentDevice))) {
                         routeHfpAudio(false);
+                        returnAudioFocusIfNecessary();
                     }
                     break;
 
@@ -1564,10 +1663,7 @@
                 case HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED:
                     if (mCurrentDevice.equals(device)) {
                         processAudioEvent(HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED,
-                            device);
-                        broadcastConnectionState(mCurrentDevice,
-                                BluetoothProfile.STATE_DISCONNECTED,
-                                BluetoothProfile.STATE_CONNECTED);
+                                device);
                         transitionTo(mDisconnected);
                     } else {
                         Log.e(TAG, "Disconnected from unknown device: " + device);
@@ -1595,8 +1691,7 @@
                     // is not much we can do here since dropping the call without user consent
                     // even if the audio connection snapped may not be a good idea.
                     routeHfpAudio(false);
-                    broadcastAudioState(device, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
-                            BluetoothHeadsetClient.STATE_AUDIO_CONNECTED);
+                    returnAudioFocusIfNecessary();
                     transitionTo(mConnected);
                     break;
 
@@ -1611,6 +1706,9 @@
             if (DBG) {
                 Log.d(TAG, "Exit AudioOn: " + getCurrentMessage().what);
             }
+            mPrevState = this;
+            broadcastAudioState(mCurrentDevice, BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED,
+                    BluetoothHeadsetClient.STATE_AUDIO_CONNECTED);
         }
     }
 
@@ -1643,11 +1741,9 @@
         Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
         intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
-
         if (newState == BluetoothHeadsetClient.STATE_AUDIO_CONNECTED) {
             intent.putExtra(BluetoothHeadsetClient.EXTRA_AUDIO_WBS, mAudioWbs);
         }
-
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
         if (DBG) {
@@ -1656,8 +1752,7 @@
     }
 
     // This method does not check for error condition (newState == prevState)
-    protected void broadcastConnectionState
-            (BluetoothDevice device, int newState, int prevState) {
+    private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
         if (DBG) {
             Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
         }
@@ -1674,38 +1769,40 @@
 
         // add feature extras when connected
         if (newState == BluetoothProfile.STATE_CONNECTED) {
-            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY) ==
-                    HeadsetClientHalConstants.PEER_FEAT_3WAY) {
+            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_3WAY)
+                    == HeadsetClientHalConstants.PEER_FEAT_3WAY) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING, true);
             }
-            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT) ==
-                    HeadsetClientHalConstants.PEER_FEAT_REJECT) {
+            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_REJECT)
+                    == HeadsetClientHalConstants.PEER_FEAT_REJECT) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL, true);
             }
-            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC) ==
-                    HeadsetClientHalConstants.PEER_FEAT_ECC) {
+            if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_ECC)
+                    == HeadsetClientHalConstants.PEER_FEAT_ECC) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, true);
             }
 
             // add individual CHLD support extras
-            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) ==
-                    HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {
-                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL, true);
+            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC)
+                    == HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC) {
+                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL,
+                        true);
             }
-            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL) ==
-                    HeadsetClientHalConstants.CHLD_FEAT_REL) {
-                intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL, true);
+            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL)
+                    == HeadsetClientHalConstants.CHLD_FEAT_REL) {
+                intent.putExtra(
+                        BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL, true);
             }
-            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) ==
-                    HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {
+            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_REL_ACC)
+                    == HeadsetClientHalConstants.CHLD_FEAT_REL_ACC) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT, true);
             }
-            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE) ==
-                    HeadsetClientHalConstants.CHLD_FEAT_MERGE) {
+            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE)
+                    == HeadsetClientHalConstants.CHLD_FEAT_MERGE) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE, true);
             }
-            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) ==
-                    HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
+            if ((mChldFeatures & HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH)
+                    == HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH) {
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
             }
         }
@@ -1746,12 +1843,10 @@
         // 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))) {
-            if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
-                ret = true;
-            }
+        if ((BluetoothProfile.PRIORITY_OFF < priority) || (
+                (BluetoothProfile.PRIORITY_UNDEFINED == priority) && (device.getBondState()
+                        != BluetoothDevice.BOND_NONE))) {
+            ret = true;
         }
         return ret;
     }
diff --git a/src/com/android/bluetooth/hfpclient/NativeInterface.java b/src/com/android/bluetooth/hfpclient/NativeInterface.java
index a9b3115..77d9af7 100644
--- a/src/com/android/bluetooth/hfpclient/NativeInterface.java
+++ b/src/com/android/bluetooth/hfpclient/NativeInterface.java
@@ -20,39 +20,55 @@
  */
 package com.android.bluetooth.hfpclient;
 
-import com.android.bluetooth.Utils;
-
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.util.Log;
 
 class NativeInterface {
-    private static String TAG = "NativeInterface";
-    private static boolean DBG = false;
+    private static final String TAG = "NativeInterface";
+    private static final boolean DBG = false;
 
     NativeInterface() {}
 
     // Native methods that call into the JNI interface
     static native void classInitNative();
+
     static native void initializeNative();
+
     static native void cleanupNative();
+
     static native boolean connectNative(byte[] address);
+
     static native boolean disconnectNative(byte[] address);
+
     static native boolean connectAudioNative(byte[] address);
+
     static native boolean disconnectAudioNative(byte[] address);
+
     static native boolean startVoiceRecognitionNative(byte[] address);
+
     static native boolean stopVoiceRecognitionNative(byte[] address);
+
     static native boolean setVolumeNative(byte[] address, int volumeType, int volume);
+
     static native boolean dialNative(byte[] address, String number);
+
     static native boolean dialMemoryNative(byte[] address, int location);
+
     static native boolean handleCallActionNative(byte[] address, int action, int index);
+
     static native boolean queryCurrentCallsNative(byte[] address);
+
     static native boolean queryCurrentOperatorNameNative(byte[] address);
+
     static native boolean retrieveSubscriberInfoNative(byte[] address);
+
     static native boolean sendDtmfNative(byte[] address, byte code);
+
     static native boolean requestLastVoiceTagNumberNative(byte[] address);
-    static native boolean sendATCmdNative(byte[] address, int atCmd, int val1,
-            int val2, String arg);
+
+    static native boolean sendATCmdNative(byte[] address, int atCmd, int val1, int val2,
+            String arg);
 
     private BluetoothDevice getDevice(byte[] address) {
         return BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
@@ -60,15 +76,16 @@
 
     // Callbacks from the native back into the java framework. All callbacks are routed via the
     // Service which will disambiguate which state machine the message should be routed through.
-    private void onConnectionStateChanged(int state, int peer_feat, int chld_feat, byte[] address) {
+    private void onConnectionStateChanged(int state, int peerFeat, int chldFeat, byte[] address) {
         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
         event.valueInt = state;
-        event.valueInt2 = peer_feat;
-        event.valueInt3 = chld_feat;
+        event.valueInt2 = peerFeat;
+        event.valueInt3 = chldFeat;
         event.device = getDevice(address);
-        // BluetoothAdapter.getDefaultAdapter().getRemoteDevice(Utils.getAddressStringFromByte(address));
+        // BluetoothAdapter.getDefaultAdapter().getRemoteDevice(Utils.getAddressStringFromByte
+        // (address));
         if (DBG) {
-            Log.d(TAG, "Device addr "  + event.device.getAddress() + " State " + state);
+            Log.d(TAG, "Device addr " + event.device.getAddress() + " State " + state);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -83,18 +100,32 @@
         event.valueInt = state;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onAudioStateChanged: address " + address + " event "  + event);
+            Log.d(TAG, "onAudioStateChanged: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
             service.messageFromNative(event);
         } else {
-            Log.w(TAG, "onAudioStateChanged: Ignoring message because service not available: " + event);
+            Log.w(TAG, "onAudioStateChanged: Ignoring message because service not available: "
+                    + event);
         }
     }
 
-    private void onVrStateChanged(int state) {
-        Log.w(TAG, "onVrStateChanged not supported");
+    private void onVrStateChanged(int state, byte[] address) {
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_VR_STATE_CHANGED);
+        event.valueInt = state;
+        event.device = getDevice(address);
+        if (DBG) {
+            Log.d(TAG, "onVrStateChanged: address " + address + " event " + event);
+        }
+
+        HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
+        if (service != null) {
+            service.messageFromNative(event);
+        } else {
+            Log.w(TAG,
+                    "onVrStateChanged: Ignoring message because service not available: " + event);
+        }
     }
 
     private void onNetworkState(int state, byte[] address) {
@@ -102,14 +133,16 @@
         event.valueInt = state;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onVrStateChanged: address " + address + " event "  + event);
+            Log.d(TAG, "onNetworkStateChanged: address " + address + " event " + event);
         }
 
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
             service.messageFromNative(event);
         } else {
-            Log.w(TAG, "onVrStateChanged: Ignoring message because service not available: " + event);
+            Log.w(TAG,
+                    "onNetworkStateChanged: Ignoring message because service not available: "
+                            + event);
         }
     }
 
@@ -124,7 +157,8 @@
         if (service != null) {
             service.messageFromNative(event);
         } else {
-            Log.w(TAG, "onNetworkRoaming: Ignoring message because service not available: " + event);
+            Log.w(TAG,
+                    "onNetworkRoaming: Ignoring message because service not available: " + event);
         }
     }
 
@@ -133,7 +167,7 @@
         event.valueInt = signal;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onNetworkSignal: address " + address + " event "  + event);
+            Log.d(TAG, "onNetworkSignal: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -148,7 +182,7 @@
         event.valueInt = level;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onBatteryLevel: address " + address + " event "  + event);
+            Log.d(TAG, "onBatteryLevel: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -163,13 +197,14 @@
         event.valueString = name;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onCurrentOperator: address " + address + " event "  + event);
+            Log.d(TAG, "onCurrentOperator: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
             service.messageFromNative(event);
         } else {
-            Log.w(TAG, "onCurrentOperator: Ignoring message because service not available: " + event);
+            Log.w(TAG,
+                    "onCurrentOperator: Ignoring message because service not available: " + event);
         }
     }
 
@@ -178,7 +213,7 @@
         event.valueInt = call;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onCall: address " + address + " event "  + event);
+            Log.d(TAG, "onCall: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -203,7 +238,7 @@
         event.device = getDevice(address);
         if (DBG) {
             Log.d(TAG, "onCallSetup: addr " + address + " device" + event.device);
-            Log.d(TAG, "onCallSetup: address " + address + " event "  + event);
+            Log.d(TAG, "onCallSetup: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -227,7 +262,7 @@
         event.valueInt = callheld;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onCallHeld: address " + address + " event "  + event);
+            Log.d(TAG, "onCallHeld: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -237,12 +272,12 @@
         }
     }
 
-    private void onRespAndHold(int resp_and_hold, byte[] address) {
+    private void onRespAndHold(int respAndHold, byte[] address) {
         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_RESP_AND_HOLD);
-        event.valueInt = resp_and_hold;
+        event.valueInt = respAndHold;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onRespAndHold: address " + address + " event "  + event);
+            Log.d(TAG, "onRespAndHold: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -257,7 +292,7 @@
         event.valueString = number;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onClip: address " + address + " event "  + event);
+            Log.d(TAG, "onClip: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -272,7 +307,7 @@
         event.valueString = number;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onCallWaiting: address " + address + " event "  + event);
+            Log.d(TAG, "onCallWaiting: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -282,8 +317,8 @@
         }
     }
 
-    private void onCurrentCalls(
-            int index, int dir, int state, int mparty, String number, byte[] address) {
+    private void onCurrentCalls(int index, int dir, int state, int mparty, String number,
+            byte[] address) {
         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CURRENT_CALLS);
         event.valueInt = index;
         event.valueInt2 = dir;
@@ -292,7 +327,7 @@
         event.valueString = number;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onCurrentCalls: address " + address + " event "  + event);
+            Log.d(TAG, "onCurrentCalls: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -308,7 +343,7 @@
         event.valueInt2 = volume;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onVolumeChange: address " + address + " event "  + event);
+            Log.d(TAG, "onVolumeChange: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -324,7 +359,7 @@
         event.valueInt2 = cme;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onCmdResult: address " + address + " event "  + event);
+            Log.d(TAG, "onCmdResult: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
@@ -340,18 +375,31 @@
         event.valueString = number;
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onSubscriberInfo: address " + address + " event "  + event);
+            Log.d(TAG, "onSubscriberInfo: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
             service.messageFromNative(event);
         } else {
-            Log.w(TAG, "onSubscriberInfo: Ignoring message because service not available: " + event);
+            Log.w(TAG,
+                    "onSubscriberInfo: Ignoring message because service not available: " + event);
         }
     }
 
-    private void onInBandRing(int in_band, byte[] address) {
-        Log.w(TAG, "onInBandRing not supported");
+    private void onInBandRing(int inBand, byte[] address) {
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
+        event.valueInt = inBand;
+        event.device = getDevice(address);
+        if (DBG) {
+            Log.d(TAG, "onInBandRing: address " + address + " event " + event);
+        }
+        HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
+        if (service != null) {
+            service.messageFromNative(event);
+        } else {
+            Log.w(TAG,
+                    "onInBandRing: Ignoring message because service not available: " + event);
+        }
     }
 
     private void onLastVoiceTagNumber(String number, byte[] address) {
@@ -362,13 +410,14 @@
         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_RING_INDICATION);
         event.device = getDevice(address);
         if (DBG) {
-            Log.d(TAG, "onRingIndication: address " + address + " event "  + event);
+            Log.d(TAG, "onRingIndication: address " + address + " event " + event);
         }
         HeadsetClientService service = HeadsetClientService.getHeadsetClientService();
         if (service != null) {
             service.messageFromNative(event);
         } else {
-            Log.w(TAG, "onRingIndication: Ignoring message because service not available: " + event);
+            Log.w(TAG,
+                    "onRingIndication: 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 070feb1..de2a338 100644
--- a/src/com/android/bluetooth/hfpclient/StackEvent.java
+++ b/src/com/android/bluetooth/hfpclient/StackEvent.java
@@ -23,36 +23,38 @@
 
 public class StackEvent {
     // Type of event that signifies a native event and consumed by state machine
-    final public static int STACK_EVENT = 100;
+    public static final int STACK_EVENT = 100;
 
     // Event types for STACK_EVENT message (coming from native)
-    final public static int EVENT_TYPE_NONE = 0;
-    final public static int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
-    final public static int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
-    final public static int EVENT_TYPE_NETWORK_STATE = 4;
-    final public static int EVENT_TYPE_ROAMING_STATE = 5;
-    final public static int EVENT_TYPE_NETWORK_SIGNAL = 6;
-    final public static int EVENT_TYPE_BATTERY_LEVEL = 7;
-    final public static int EVENT_TYPE_OPERATOR_NAME = 8;
-    final public static int EVENT_TYPE_CALL = 9;
-    final public static int EVENT_TYPE_CALLSETUP = 10;
-    final public static int EVENT_TYPE_CALLHELD = 11;
-    final public static int EVENT_TYPE_CLIP = 12;
-    final public static int EVENT_TYPE_CALL_WAITING = 13;
-    final public static int EVENT_TYPE_CURRENT_CALLS = 14;
-    final public static int EVENT_TYPE_VOLUME_CHANGED = 15;
-    final public static int EVENT_TYPE_CMD_RESULT = 16;
-    final public static int EVENT_TYPE_SUBSCRIBER_INFO = 17;
-    final public static int EVENT_TYPE_RESP_AND_HOLD = 18;
-    final public static int EVENT_TYPE_RING_INDICATION= 21;
+    public static final int EVENT_TYPE_NONE = 0;
+    public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+    public static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2;
+    public static final int EVENT_TYPE_VR_STATE_CHANGED = 3;
+    public static final int EVENT_TYPE_NETWORK_STATE = 4;
+    public static final int EVENT_TYPE_ROAMING_STATE = 5;
+    public static final int EVENT_TYPE_NETWORK_SIGNAL = 6;
+    public static final int EVENT_TYPE_BATTERY_LEVEL = 7;
+    public static final int EVENT_TYPE_OPERATOR_NAME = 8;
+    public static final int EVENT_TYPE_CALL = 9;
+    public static final int EVENT_TYPE_CALLSETUP = 10;
+    public static final int EVENT_TYPE_CALLHELD = 11;
+    public static final int EVENT_TYPE_RESP_AND_HOLD = 12;
+    public static final int EVENT_TYPE_CLIP = 13;
+    public static final int EVENT_TYPE_CALL_WAITING = 14;
+    public static final int EVENT_TYPE_CURRENT_CALLS = 15;
+    public static final int EVENT_TYPE_VOLUME_CHANGED = 16;
+    public static final int EVENT_TYPE_CMD_RESULT = 17;
+    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;
 
-    int type = EVENT_TYPE_NONE;
-    int valueInt = 0;
-    int valueInt2 = 0;
-    int valueInt3 = 0;
-    int valueInt4 = 0;
-    String valueString = null;
-    BluetoothDevice device = null;
+    public int type = EVENT_TYPE_NONE;
+    public int valueInt = 0;
+    public int valueInt2 = 0;
+    public int valueInt3 = 0;
+    public int valueInt4 = 0;
+    public String valueString = null;
+    public BluetoothDevice device = null;
 
     StackEvent(int type) {
         this.type = type;
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConference.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConference.java
index 954b8b8..e9166b9 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConference.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConference.java
@@ -17,32 +17,26 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
-import android.bluetooth.BluetoothHeadsetClientCall;
-import android.os.Bundle;
 import android.telecom.Conference;
 import android.telecom.Connection;
-import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccountHandle;
 import android.util.Log;
 
-import java.util.List;
-import java.util.ArrayList;
-
 public class HfpClientConference extends Conference {
     private static final String TAG = "HfpClientConference";
 
     private BluetoothDevice mDevice;
     private BluetoothHeadsetClient mHeadsetProfile;
 
-    public HfpClientConference(PhoneAccountHandle handle,
-            BluetoothDevice device, BluetoothHeadsetClient client) {
+    public HfpClientConference(PhoneAccountHandle handle, BluetoothDevice device,
+            BluetoothHeadsetClient client) {
         super(handle);
         mDevice = device;
         mHeadsetProfile = client;
         boolean manage = HfpClientConnectionService.hasHfpClientEcc(client, device);
-        setConnectionCapabilities(Connection.CAPABILITY_SUPPORT_HOLD |
-                Connection.CAPABILITY_HOLD |
-                (manage ? Connection.CAPABILITY_MANAGE_CONFERENCE : 0));
+        setConnectionCapabilities(
+                Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD | (manage
+                        ? Connection.CAPABILITY_MANAGE_CONFERENCE : 0));
         setActive();
     }
 
@@ -88,11 +82,11 @@
     @Override
     public void onConnectionAdded(Connection connection) {
         Log.d(TAG, "onConnectionAdded " + connection);
-        if (connection.getState() == Connection.STATE_HOLDING &&
-                getState() == Connection.STATE_ACTIVE) {
+        if (connection.getState() == Connection.STATE_HOLDING
+                && getState() == Connection.STATE_ACTIVE) {
             connection.onAnswer();
-        } else if (connection.getState() == Connection.STATE_ACTIVE &&
-                getState() == Connection.STATE_HOLDING) {
+        } else if (connection.getState() == Connection.STATE_ACTIVE
+                && getState() == Connection.STATE_HOLDING) {
             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
         }
     }
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
index 0aca701..1d2add4 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
@@ -18,7 +18,6 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothHeadsetClientCall;
-import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.net.Uri;
 import android.telecom.Connection;
@@ -74,8 +73,7 @@
             throw new IllegalStateException("HeadsetProfile is null, returning");
         }
 
-        mCurrentCall = mHeadsetProfile.dial(
-            mDevice, number.getSchemeSpecificPart());
+        mCurrentCall = mHeadsetProfile.dial(mDevice, number.getSchemeSpecificPart());
         if (mCurrentCall == null) {
             close(DisconnectCause.ERROR);
             Log.e(TAG, "Failed to create the call, dial failed.");
@@ -93,9 +91,11 @@
         setAudioModeIsVoip(false);
         Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, mCurrentCall.getNumber(), null);
         setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
-        setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
-                CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
-                (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
+        setConnectionCapabilities(
+                CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE | CAPABILITY_SEPARATE_FROM_CONFERENCE
+                        | CAPABILITY_DISCONNECT_FROM_CONFERENCE | (
+                        getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD
+                                : 0));
     }
 
     public UUID getUUID() {
@@ -116,8 +116,8 @@
     }
 
     public boolean inConference() {
-        return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
-                getState() != Connection.STATE_DISCONNECTED;
+        return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty()
+                && getState() != Connection.STATE_DISCONNECTED;
     }
 
     public void enterPrivateMode() {
@@ -289,7 +289,7 @@
 
     @Override
     public String toString() {
-        return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
-                mCurrentCall + "}";
+        return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + ","
+                + mCurrentCall + "}";
     }
 }
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
index 876041d..af46122 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
@@ -27,11 +27,9 @@
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
 import android.telecom.ConnectionService;
-import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -40,14 +38,11 @@
 import com.android.bluetooth.hfpclient.HeadsetClientService;
 
 import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.UUID;
 
 public class HfpClientConnectionService extends ConnectionService {
     private static final String TAG = "HfpClientConnService";
@@ -61,8 +56,7 @@
     private BluetoothHeadsetClient mHeadsetProfile;
     private TelecomManager mTelecomManager;
 
-    private final Map<BluetoothDevice, HfpClientDeviceBlock> mDeviceBlocks =
-        new HashMap<>();
+    private final Map<BluetoothDevice, HfpClientDeviceBlock> mDeviceBlocks = new HashMap<>();
 
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -104,7 +98,7 @@
                 }
             } else if (BluetoothHeadsetClient.ACTION_CALL_CHANGED.equals(action)) {
                 BluetoothHeadsetClientCall call =
-                    intent.getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL);
+                        intent.getParcelableExtra(BluetoothHeadsetClient.EXTRA_CALL);
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                 HfpClientDeviceBlock block = findBlockForDevice(call.getDevice());
                 if (block == null) {
@@ -128,6 +122,7 @@
         }
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         mTelecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+        if (mTelecomManager != null) mTelecomManager.clearPhoneAccounts();
         mAdapter.getProfileProxy(this, mServiceListener, BluetoothProfile.HEADSET_CLIENT);
     }
 
@@ -155,7 +150,7 @@
 
     private synchronized void disconnectAll() {
         for (Iterator<Map.Entry<BluetoothDevice, HfpClientDeviceBlock>> it =
-                mDeviceBlocks.entrySet().iterator(); it.hasNext();) {
+                mDeviceBlocks.entrySet().iterator(); it.hasNext(); ) {
             it.next().getValue().cleanup();
             it.remove();
         }
@@ -169,8 +164,8 @@
         // In order to make sure that the service is sticky (recovers from errors when HFP
         // connection is still active) and to stop it we need a special intent since stopService
         // only recreates it.
-        if (intent != null &&
-            intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG, false)) {
+        if (intent != null && intent.getBooleanExtra(HeadsetClientService.HFP_CLIENT_STOP_TAG,
+                false)) {
             // Stop the service.
             stopSelf();
             return 0;
@@ -185,12 +180,11 @@
 
     // This method is called whenever there is a new incoming call (or right after BT connection).
     @Override
-    public Connection onCreateIncomingConnection(
-            PhoneAccountHandle connectionManagerAccount,
+    public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerAccount,
             ConnectionRequest request) {
         if (DBG) {
-            Log.d(TAG, "onCreateIncomingConnection " + connectionManagerAccount +
-                " req: " + request);
+            Log.d(TAG,
+                    "onCreateIncomingConnection " + connectionManagerAccount + " req: " + request);
         }
 
         HfpClientDeviceBlock block = findBlockForHandle(connectionManagerAccount);
@@ -201,15 +195,13 @@
 
         // We should already have a connection by this time.
         BluetoothHeadsetClientCall call =
-            request.getExtras().getParcelable(
-                TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
+                request.getExtras().getParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS);
         return block.onCreateIncomingConnection(call);
     }
 
     // This method is called *only if* Dialer UI is used to place an outgoing call.
     @Override
-    public Connection onCreateOutgoingConnection(
-            PhoneAccountHandle connectionManagerAccount,
+    public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerAccount,
             ConnectionRequest request) {
         if (DBG) {
             Log.d(TAG, "onCreateOutgoingConnection " + connectionManagerAccount);
@@ -227,8 +219,7 @@
     // 1. Outgoing call created from the AG.
     // 2. Call transfer from AG -> HF (on connection when existed call present).
     @Override
-    public Connection onCreateUnknownConnection(
-            PhoneAccountHandle connectionManagerAccount,
+    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerAccount,
             ConnectionRequest request) {
         if (DBG) {
             Log.d(TAG, "onCreateUnknownConnection " + connectionManagerAccount);
@@ -241,8 +232,7 @@
 
         // We should already have a connection by this time.
         BluetoothHeadsetClientCall call =
-            request.getExtras().getParcelable(
-                TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
+                request.getExtras().getParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
         return block.onCreateUnknownConnection(call);
     }
 
@@ -256,9 +246,9 @@
         BluetoothDevice bd2 = ((HfpClientConnection) connection2).getDevice();
         // We can only conference two connections on same device
         if (!Objects.equals(bd1, bd2)) {
-            Log.e(TAG, "Cannot conference calls from two different devices "
-                            + "bd1 " + bd1 + " bd2 " + bd2 + " conn1 " + connection1
-                            + "connection2 " + connection2);
+            Log.e(TAG,
+                    "Cannot conference calls from two different devices " + "bd1 " + bd1 + " bd2 "
+                            + bd2 + " conn1 " + connection1 + "connection2 " + connection2);
             return;
         }
 
@@ -335,14 +325,14 @@
     // Util functions that may be used by various classes
     public static PhoneAccount createAccount(Context context, BluetoothDevice device) {
         Uri addr = Uri.fromParts(HfpClientConnectionService.HFP_SCHEME, device.getAddress(), null);
-        PhoneAccountHandle handle = new PhoneAccountHandle(
-            new ComponentName(context, HfpClientConnectionService.class), device.getAddress());
+        PhoneAccountHandle handle =
+                new PhoneAccountHandle(new ComponentName(context, HfpClientConnectionService.class),
+                        device.getAddress());
         PhoneAccount account =
-                new PhoneAccount.Builder(handle, "HFP " + device.toString())
-                    .setAddress(addr)
-                    .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
-                    .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
-                    .build();
+                new PhoneAccount.Builder(handle, "HFP " + device.toString()).setAddress(addr)
+                        .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
+                        .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                        .build();
         if (DBG) {
             Log.d(TAG, "phoneaccount: " + account);
         }
@@ -351,7 +341,7 @@
 
     public static boolean hasHfpClientEcc(BluetoothHeadsetClient client, BluetoothDevice device) {
         Bundle features = client.getCurrentAgEvents(device);
-        return features == null ? false :
-                features.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC, false);
+        return features != null && features.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC,
+                false);
     }
 }
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
index f891b90..05af73e 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
@@ -15,33 +15,18 @@
  */
 package com.android.bluetooth.hfpclient.connserv;
 
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothHeadsetClientCall;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
 import android.telecom.Connection;
-import android.telecom.ConnectionRequest;
-import android.telecom.ConnectionService;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.util.Log;
 
-import com.android.bluetooth.hfpclient.HeadsetClientService;
-
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -53,8 +38,8 @@
 // Lifecycle of a Device Block is managed entirely by the Service which creates it. In essence it
 // has only the active state otherwise the block should be GCed.
 public class HfpClientDeviceBlock {
-    private final String TAG;
-    private final boolean DBG = false;
+    private final String mTAG;
+    private static final boolean DBG = false;
     private final Context mContext;
     private final BluetoothDevice mDevice;
     private final PhoneAccount mPhoneAccount;
@@ -65,14 +50,12 @@
 
     private BluetoothHeadsetClient mHeadsetProfile;
 
-    HfpClientDeviceBlock(
-            HfpClientConnectionService connServ,
-            BluetoothDevice device,
+    HfpClientDeviceBlock(HfpClientConnectionService connServ, BluetoothDevice device,
             BluetoothHeadsetClient headsetProfile) {
         mConnServ = connServ;
         mContext = connServ;
         mDevice = device;
-        TAG = "HfpClientDeviceBlock." + mDevice.getAddress();
+        mTAG = "HfpClientDeviceBlock." + mDevice.getAddress();
         mPhoneAccount = HfpClientConnectionService.createAccount(mContext, device);
         mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
 
@@ -84,16 +67,15 @@
 
         // Read the current calls and add them to telecom if already present
         if (mHeadsetProfile != null) {
-            List<BluetoothHeadsetClientCall> calls =
-                    mHeadsetProfile.getCurrentCalls(mDevice);
+            List<BluetoothHeadsetClientCall> calls = mHeadsetProfile.getCurrentCalls(mDevice);
             if (DBG) {
-                Log.d(TAG, "Got calls " + calls);
+                Log.d(mTAG, "Got calls " + calls);
             }
             if (calls == null) {
                 // We can get null as a return if we are not connected. Hence there may
                 // be a race in getting the broadcast and HFP Client getting
                 // disconnected before broadcast gets delivered.
-                Log.w(TAG, "Got connected but calls were null, ignoring the broadcast");
+                Log.w(mTAG, "Got connected but calls were null, ignoring the broadcast");
                 return;
             }
 
@@ -101,18 +83,17 @@
                 handleCall(call);
             }
         } else {
-            Log.e(TAG, "headset profile is null, ignoring broadcast.");
+            Log.e(mTAG, "headset profile is null, ignoring broadcast.");
         }
     }
 
     synchronized Connection onCreateIncomingConnection(BluetoothHeadsetClientCall call) {
-        HfpClientConnection connection = connection = mConnections.get(call.getUUID());
+        HfpClientConnection connection = mConnections.get(call.getUUID());
         if (connection != null) {
             connection.onAdded();
-            updateConferenceableConnections();
             return connection;
         } else {
-            Log.e(TAG, "Call " + call + " ignored: connection does not exist");
+            Log.e(mTAG, "Call " + call + " ignored: connection does not exist");
             return null;
         }
     }
@@ -127,22 +108,21 @@
 
     synchronized Connection onCreateUnknownConnection(BluetoothHeadsetClientCall call) {
         Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null);
-        HfpClientConnection connection = connection = mConnections.get(call.getUUID());
+        HfpClientConnection connection = mConnections.get(call.getUUID());
 
         if (connection != null) {
             connection.onAdded();
-            updateConferenceableConnections();
             return connection;
         } else {
-            Log.e(TAG, "Call " + call + " ignored: connection does not exist");
+            Log.e(mTAG, "Call " + call + " ignored: connection does not exist");
             return null;
         }
     }
 
     synchronized void onConference(Connection connection1, Connection connection2) {
         if (mConference == null) {
-            mConference = new HfpClientConference(
-                mPhoneAccount.getAccountHandle(), mDevice, mHeadsetProfile);
+            mConference = new HfpClientConference(mPhoneAccount.getAccountHandle(), mDevice,
+                    mHeadsetProfile);
         }
 
         if (connection1.getConference() == null) {
@@ -157,7 +137,7 @@
     // Remove existing calls and the phone account associated, the object will get garbage
     // collected soon
     synchronized void cleanup() {
-        Log.d(TAG, "Resetting state for device " + mDevice);
+        Log.d(mTAG, "Resetting state for device " + mDevice);
         disconnectAll();
         mTelecomManager.unregisterPhoneAccount(mPhoneAccount.getAccountHandle());
     }
@@ -165,7 +145,7 @@
     // Handle call change
     synchronized void handleCall(BluetoothHeadsetClientCall call) {
         if (DBG) {
-            Log.d(TAG, "Got call " + call.toString(true));
+            Log.d(mTAG, "Got call " + call.toString(true));
         }
 
         HfpClientConnection connection = findConnectionKey(call);
@@ -194,7 +174,8 @@
             Bundle b = new Bundle();
             if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_DIALING
                     || call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ALERTING
-                    || call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ACTIVE) {
+                    || call.getState() == BluetoothHeadsetClientCall.CALL_STATE_ACTIVE
+                    || call.getState() == BluetoothHeadsetClientCall.CALL_STATE_HELD) {
                 // This is an outgoing call. Even if it is an active call we do not have a way of
                 // putting that parcelable in a seaprate field.
                 b.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, call);
@@ -203,11 +184,12 @@
                     || call.getState() == BluetoothHeadsetClientCall.CALL_STATE_WAITING) {
                 // This is an incoming call.
                 b.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, call);
+                b.putBoolean(TelecomManager.EXTRA_CALL_EXTERNAL_RINGER, call.isInBandRing());
                 mTelecomManager.addNewIncomingCall(mPhoneAccount.getAccountHandle(), b);
             }
         } else if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
             if (DBG) {
-                Log.d(TAG, "Removing call " + call);
+                Log.d(mTAG, "Removing call " + call);
             }
             mConnections.remove(call.getUUID());
         }
@@ -218,7 +200,7 @@
     // Find the connection specified by the key, also update the key with ID if present.
     private synchronized HfpClientConnection findConnectionKey(BluetoothHeadsetClientCall call) {
         if (DBG) {
-            Log.d(TAG, "findConnectionKey local key set " + mConnections.toString());
+            Log.d(mTAG, "findConnectionKey local key set " + mConnections.toString());
         }
         return mConnections.get(call.getUUID());
     }
@@ -240,29 +222,30 @@
     private boolean isDisconnectingToActive(HfpClientConnection prevConn,
             BluetoothHeadsetClientCall newCall) {
         if (DBG) {
-            Log.d(TAG, "prevConn " + prevConn.isClosing() + " new call " + newCall.getState());
+            Log.d(mTAG, "prevConn " + prevConn.isClosing() + " new call " + newCall.getState());
         }
-        if (prevConn.isClosing() &&
-                newCall.getState() != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
+        if (prevConn.isClosing()
+                && newCall.getState() != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
             return true;
         }
         return false;
     }
 
-    private synchronized HfpClientConnection buildConnection(
-            BluetoothHeadsetClientCall call, Uri number) {
+    private synchronized HfpClientConnection buildConnection(BluetoothHeadsetClientCall call,
+            Uri number) {
         if (mHeadsetProfile == null) {
-            Log.e(TAG, "Cannot create connection for call " + call + " when Profile not available");
+            Log.e(mTAG,
+                    "Cannot create connection for call " + call + " when Profile not available");
             return null;
         }
 
         if (call == null && number == null) {
-            Log.e(TAG, "Both call and number cannot be null.");
+            Log.e(mTAG, "Both call and number cannot be null.");
             return null;
         }
 
         if (DBG) {
-            Log.d(TAG, "Creating connection on " + mDevice + " for " + call + "/" + number);
+            Log.d(mTAG, "Creating connection on " + mDevice + " for " + call + "/" + number);
         }
 
         HfpClientConnection connection = null;
@@ -283,8 +266,8 @@
     private void updateConferenceableConnections() {
         boolean addConf = false;
         if (DBG) {
-            Log.d(TAG, "Existing connections: " + mConnections + " existing conference " +
-                mConference);
+            Log.d(mTAG, "Existing connections: " + mConnections + " existing conference "
+                    + mConference);
         }
 
         // If we have an existing conference call then loop through all connections and update any
@@ -293,7 +276,7 @@
             for (Connection confConn : mConference.getConnections()) {
                 if (!((HfpClientConnection) confConn).inConference()) {
                     if (DBG) {
-                        Log.d(TAG, "Removing connection " + confConn + " from conference.");
+                        Log.d(mTAG, "Removing connection " + confConn + " from conference.");
                     }
                     mConference.removeConnection(confConn);
                 }
@@ -307,12 +290,12 @@
             if (((HfpClientConnection) otherConn).inConference()) {
                 // If this is the first connection with conference, create the conference first.
                 if (mConference == null) {
-                    mConference = new HfpClientConference(
-                        mPhoneAccount.getAccountHandle(), mDevice, mHeadsetProfile);
+                    mConference = new HfpClientConference(mPhoneAccount.getAccountHandle(), mDevice,
+                            mHeadsetProfile);
                 }
                 if (mConference.addConnection(otherConn)) {
                     if (DBG) {
-                        Log.d(TAG, "Adding connection " + otherConn + " to conference.");
+                        Log.d(mTAG, "Adding connection " + otherConn + " to conference.");
                     }
                     addConf = true;
                 }
@@ -322,7 +305,7 @@
         // If we have no connections in the conference we should simply end it.
         if (mConference != null && mConference.getConnections().size() == 0) {
             if (DBG) {
-                Log.d(TAG, "Conference has no connection, destroying");
+                Log.d(mTAG, "Conference has no connection, destroying");
             }
             mConference.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
             mConference.destroy();
@@ -332,7 +315,7 @@
         // If we have a valid conference and not previously added then add it.
         if (mConference != null && addConf) {
             if (DBG) {
-                Log.d(TAG, "Adding conference to stack.");
+                Log.d(mTAG, "Adding conference to stack.");
             }
             mConnServ.addConference(mConference);
         }
diff --git a/src/com/android/bluetooth/hid/HidDevService.java b/src/com/android/bluetooth/hid/HidDevService.java
deleted file mode 100644
index 4f0993b..0000000
--- a/src/com/android/bluetooth/hid/HidDevService.java
+++ /dev/null
@@ -1,698 +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.hid;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHidDeviceAppConfiguration;
-import android.bluetooth.BluetoothHidDeviceAppQosSettings;
-import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
-import android.bluetooth.BluetoothInputHost;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.IBluetoothHidDeviceCallback;
-import android.bluetooth.IBluetoothInputHost;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.bluetooth.Utils;
-import com.android.bluetooth.btservice.ProfileService;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-/** @hide */
-public class HidDevService extends ProfileService {
-    private static final boolean DBG = false;
-
-    private static final String TAG = HidDevService.class.getSimpleName();
-
-    private static final int MESSAGE_APPLICATION_STATE_CHANGED = 1;
-    private static final int MESSAGE_CONNECT_STATE_CHANGED = 2;
-    private static final int MESSAGE_GET_REPORT = 3;
-    private static final int MESSAGE_SET_REPORT = 4;
-    private static final int MESSAGE_SET_PROTOCOL = 5;
-    private static final int MESSAGE_INTR_DATA = 6;
-    private static final int MESSAGE_VC_UNPLUG = 7;
-
-    private boolean mNativeAvailable = false;
-
-    private BluetoothDevice mHidDevice = null;
-
-    private int mHidDeviceState = BluetoothInputHost.STATE_DISCONNECTED;
-
-    private BluetoothHidDeviceAppConfiguration mAppConfig = null;
-
-    private IBluetoothHidDeviceCallback mCallback = null;
-
-    private BluetoothHidDeviceDeathRecipient mDeathRcpt;
-
-    static {
-        classInitNative();
-    }
-
-    private final Handler mHandler = new Handler() {
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (DBG) Log.v(TAG, "handleMessage(): msg.what=" + msg.what);
-
-            switch (msg.what) {
-                case MESSAGE_APPLICATION_STATE_CHANGED: {
-                    BluetoothDevice device = msg.obj != null ? getDevice((byte[]) msg.obj) : null;
-                    boolean success = (msg.arg1 != 0);
-
-                    if (success) {
-                        mHidDevice = device;
-                    } else {
-                        mHidDevice = null;
-                    }
-
-                    try {
-                        if (mCallback != null)
-                            mCallback.onAppStatusChanged(device, mAppConfig, success);
-                        else
-                            break;
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "e=" + e.toString());
-                        e.printStackTrace();
-                    }
-
-                    if (success) {
-                        mDeathRcpt = new BluetoothHidDeviceDeathRecipient(
-                                HidDevService.this, mAppConfig);
-                        if (mCallback != null) {
-                            IBinder binder = mCallback.asBinder();
-                            try {
-                                binder.linkToDeath(mDeathRcpt, 0);
-                                Log.i(TAG, "IBinder.linkToDeath() ok");
-                            } catch (RemoteException e) {
-                                e.printStackTrace();
-                            }
-                        }
-                    } else if (mDeathRcpt != null) {
-                        if (mCallback != null) {
-                            IBinder binder = mCallback.asBinder();
-                            try {
-                                binder.unlinkToDeath(mDeathRcpt, 0);
-                                Log.i(TAG, "IBinder.unlinkToDeath() ok");
-                            } catch (NoSuchElementException e) {
-                                e.printStackTrace();
-                            }
-                            mDeathRcpt.cleanup();
-                            mDeathRcpt = null;
-                        }
-                    }
-
-                    if (!success) {
-                        mAppConfig = null;
-                        mCallback = null;
-                    }
-
-                    break;
-                }
-
-                case MESSAGE_CONNECT_STATE_CHANGED: {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
-                    int halState = msg.arg1;
-                    int state = convertHalState(halState);
-
-                    if (state != BluetoothInputHost.STATE_DISCONNECTED) {
-                        mHidDevice = device;
-                    }
-
-                    broadcastConnectionState(device, state);
-
-                    try {
-                        if (mCallback != null) mCallback.onConnectionStateChanged(device, state);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    break;
-                }
-
-                case MESSAGE_GET_REPORT:
-                    byte type = (byte) msg.arg1;
-                    byte id = (byte) msg.arg2;
-                    int bufferSize = msg.obj == null ? 0 : ((Integer) msg.obj).intValue();
-
-                    try {
-                        if (mCallback != null)
-                            mCallback.onGetReport(mHidDevice, type, id, bufferSize);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    break;
-
-                case MESSAGE_SET_REPORT: {
-                    byte reportType = (byte) msg.arg1;
-                    byte reportId = (byte) msg.arg2;
-                    byte[] data = ((ByteBuffer) msg.obj).array();
-
-                    try {
-                        if (mCallback != null)
-                            mCallback.onSetReport(mHidDevice, reportType, reportId, data);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    break;
-                }
-
-                case MESSAGE_SET_PROTOCOL:
-                    byte protocol = (byte) msg.arg1;
-
-                    try {
-                        if (mCallback != null) mCallback.onSetProtocol(mHidDevice, protocol);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    break;
-
-                case MESSAGE_INTR_DATA:
-                    byte reportId = (byte) msg.arg1;
-                    byte[] data = ((ByteBuffer) msg.obj).array();
-
-                    try {
-                        if (mCallback != null) mCallback.onIntrData(mHidDevice, reportId, data);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    break;
-
-                case MESSAGE_VC_UNPLUG:
-                    try {
-                        if (mCallback != null) mCallback.onVirtualCableUnplug(mHidDevice);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    mHidDevice = null;
-                    break;
-            }
-        }
-    };
-
-    private static class BluetoothHidDeviceDeathRecipient implements IBinder.DeathRecipient {
-        private HidDevService mService;
-        private BluetoothHidDeviceAppConfiguration mAppConfig;
-
-        public BluetoothHidDeviceDeathRecipient(
-                HidDevService service, BluetoothHidDeviceAppConfiguration config) {
-            mService = service;
-            mAppConfig = config;
-        }
-
-        @Override
-        public void binderDied() {
-            Log.w(TAG, "Binder died, need to unregister app :(");
-            mService.unregisterApp(mAppConfig);
-        }
-
-        public void cleanup() {
-            mService = null;
-            mAppConfig = null;
-        }
-  }
-
-  private static class BluetoothHidDeviceBinder
-      extends IBluetoothInputHost.Stub implements IProfileServiceBinder {
-
-    private static final String TAG =
-        BluetoothHidDeviceBinder.class.getSimpleName();
-
-    private HidDevService mService;
-
-    public BluetoothHidDeviceBinder(HidDevService service) {
-      mService = service;
-    }
-
-    @Override
-    public boolean cleanup() {
-      mService = null;
-      return true;
-    }
-
-    private HidDevService getService() {
-      if (!Utils.checkCaller()) {
-        Log.w(TAG, "HidDevice call not allowed for non-active user");
-        return null;
-      }
-
-      if (mService != null && mService.isAvailable()) {
-        return mService;
-      }
-
-      return null;
-    }
-
-    @Override
-    public boolean registerApp(BluetoothHidDeviceAppConfiguration config,
-                               BluetoothHidDeviceAppSdpSettings sdp,
-                               BluetoothHidDeviceAppQosSettings inQos,
-                               BluetoothHidDeviceAppQosSettings outQos,
-                               IBluetoothHidDeviceCallback callback) {
-      if (DBG)
-        Log.v(TAG, "registerApp()");
-
-      HidDevService service = getService();
-      if (service == null) {
-        return false;
-      }
-
-      return service.registerApp(config, sdp, inQos, outQos, callback);
-    }
-
-    @Override
-    public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
-      if (DBG)
-        Log.v(TAG, "unregisterApp()");
-
-      HidDevService service = getService();
-      if (service == null) {
-        return false;
-      }
-
-      return service.unregisterApp(config);
-    }
-
-    @Override
-    public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
-        if (DBG) Log.v(TAG, "sendReport(): device=" + device + "  id=" + id);
-
-        HidDevService service = getService();
-        if (service == null) {
-            return false;
-        }
-
-        return service.sendReport(device, id, data);
-    }
-
-    @Override
-    public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
-        if (DBG) Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
-
-        HidDevService service = getService();
-        if (service == null) {
-            return false;
-        }
-
-        return service.replyReport(device, type, id, data);
-    }
-
-    @Override
-    public boolean unplug(BluetoothDevice device) {
-        if (DBG) Log.v(TAG, "unplug(): device=" + device);
-
-        HidDevService service = getService();
-        if (service == null) {
-            return false;
-        }
-
-        return service.unplug(device);
-    }
-
-    @Override
-    public boolean connect(BluetoothDevice device) {
-        if (DBG) Log.v(TAG, "connect(): device=" + device);
-
-        HidDevService service = getService();
-        if (service == null) {
-            return false;
-        }
-
-        return service.connect(device);
-    }
-
-    @Override
-    public boolean disconnect(BluetoothDevice device) {
-        if (DBG) Log.v(TAG, "disconnect(): device=" + device);
-
-        HidDevService service = getService();
-        if (service == null) {
-            return false;
-        }
-
-        return service.disconnect(device);
-    }
-
-    @Override
-    public boolean reportError(BluetoothDevice device, byte error) {
-        if (DBG) Log.v(TAG, "reportError(): device=" + device + " error=" + error);
-
-        HidDevService service = getService();
-        if (service == null) {
-            return false;
-        }
-
-        return service.reportError(device, error);
-    }
-
-    @Override
-    public int getConnectionState(BluetoothDevice device) {
-        if (DBG) Log.v(TAG, "getConnectionState(): device=" + device);
-
-        HidDevService service = getService();
-        if (service == null) {
-            return BluetoothInputHost.STATE_DISCONNECTED;
-        }
-
-        return service.getConnectionState(device);
-    }
-
-    @Override
-    public List<BluetoothDevice> getConnectedDevices() {
-        if (DBG) Log.v(TAG, "getConnectedDevices()");
-
-        return getDevicesMatchingConnectionStates(new int[] {BluetoothProfile.STATE_CONNECTED});
-    }
-
-    @Override
-    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        if (DBG)
-            Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
-
-        HidDevService service = getService();
-        if (service == null) {
-            return new ArrayList<BluetoothDevice>(0);
-        }
-
-        return service.getDevicesMatchingConnectionStates(states);
-    }
-  }
-
-  @Override
-  protected IProfileServiceBinder initBinder() {
-    return new BluetoothHidDeviceBinder(this);
-  }
-
-  private boolean checkDevice(BluetoothDevice device) {
-      if (mHidDevice == null || !mHidDevice.equals(device)) {
-          Log.w(TAG, "Unknown device: " + device);
-          return false;
-      }
-      return true;
-  }
-
-  synchronized boolean registerApp(BluetoothHidDeviceAppConfiguration config,
-                                   BluetoothHidDeviceAppSdpSettings sdp,
-                                   BluetoothHidDeviceAppQosSettings inQos,
-                                   BluetoothHidDeviceAppQosSettings outQos,
-                                   IBluetoothHidDeviceCallback callback) {
-    if (DBG)
-      Log.v(TAG, "registerApp()");
-
-    if (mAppConfig != null) {
-      return false;
-    }
-
-    mAppConfig = config;
-    mCallback = callback;
-
-    return registerAppNative(sdp.name, sdp.description, sdp.provider,
-                             sdp.subclass, sdp.descriptors,
-                             inQos == null ? null : inQos.toArray(),
-                             outQos == null ? null : outQos.toArray());
-  }
-
-  synchronized boolean
-  unregisterApp(BluetoothHidDeviceAppConfiguration config) {
-    if (DBG)
-      Log.v(TAG, "unregisterApp()");
-
-    if (mAppConfig == null || config == null || !config.equals(mAppConfig)) {
-      return false;
-    }
-
-    return unregisterAppNative();
-  }
-
-  synchronized boolean sendReport(BluetoothDevice device, int id, byte[] data) {
-      if (DBG) Log.v(TAG, "sendReport(): device=" + device + " id=" + id);
-
-      if (!checkDevice(device)) {
-          return false;
-      }
-
-      return sendReportNative(id, data);
-  }
-
-  synchronized boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
-      if (DBG) Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
-
-      if (!checkDevice(device)) {
-          return false;
-      }
-
-      return replyReportNative(type, id, data);
-  }
-
-  synchronized boolean unplug(BluetoothDevice device) {
-      if (DBG) Log.v(TAG, "unplug(): device=" + device);
-
-      if (!checkDevice(device)) {
-          return false;
-      }
-
-      return unplugNative();
-  }
-
-  synchronized boolean connect(BluetoothDevice device) {
-      if (DBG) Log.v(TAG, "connect(): device=" + device);
-
-      return connectNative(Utils.getByteAddress(device));
-  }
-
-  synchronized boolean disconnect(BluetoothDevice device) {
-      if (DBG) Log.v(TAG, "disconnect(): device=" + device);
-
-      if (!checkDevice(device)) {
-          return false;
-      }
-
-      return disconnectNative();
-  }
-
-  synchronized boolean reportError(BluetoothDevice device, byte error) {
-      if (DBG) Log.v(TAG, "reportError(): device=" + device + " error=" + error);
-
-      if (!checkDevice(device)) {
-          return false;
-      }
-
-      return reportErrorNative(error);
-  }
-
-  @Override
-  protected boolean start() {
-    if (DBG)
-      Log.d(TAG, "start()");
-
-    initNative();
-    mNativeAvailable = true;
-
-    return true;
-  }
-
-  @Override
-  protected boolean stop() {
-    if (DBG)
-      Log.d(TAG, "stop()");
-
-    return true;
-  }
-
-  @Override
-  protected boolean cleanup() {
-    if (DBG)
-      Log.d(TAG, "cleanup()");
-
-    if (mNativeAvailable) {
-      cleanupNative();
-      mNativeAvailable = false;
-    }
-
-    return true;
-  }
-
-  int getConnectionState(BluetoothDevice device) {
-      if (mHidDevice != null && mHidDevice.equals(device)) {
-          return mHidDeviceState;
-      }
-      return BluetoothInputHost.STATE_DISCONNECTED;
-  }
-
-  List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-      enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-      List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
-
-      if (mHidDevice != null) {
-          for (int state : states) {
-              if (state == mHidDeviceState) {
-                  inputDevices.add(mHidDevice);
-                  break;
-              }
-          }
-      }
-      return inputDevices;
-  }
-
-  private synchronized void onApplicationStateChanged(byte[] address,
-                                                      boolean registered) {
-    if (DBG)
-      Log.v(TAG, "onApplicationStateChanged(): registered=" + registered);
-
-    Message msg = mHandler.obtainMessage(MESSAGE_APPLICATION_STATE_CHANGED);
-    msg.obj = address;
-    msg.arg1 = registered ? 1 : 0;
-    mHandler.sendMessage(msg);
-  }
-
-  private synchronized void onConnectStateChanged(byte[] address, int state) {
-    if (DBG)
-      Log.v(TAG, "onConnectStateChanged(): address=" +
-                     Arrays.toString(address) + " state=" + state);
-
-    Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
-    msg.obj = address;
-    msg.arg1 = state;
-    mHandler.sendMessage(msg);
-  }
-
-  private synchronized void onGetReport(byte type, byte id, short bufferSize) {
-    if (DBG)
-      Log.v(TAG, "onGetReport(): type=" + type + " id=" + id + " bufferSize=" +
-                     bufferSize);
-
-    Message msg = mHandler.obtainMessage(MESSAGE_GET_REPORT);
-    msg.obj = bufferSize > 0 ? new Integer(bufferSize) : null;
-    msg.arg1 = type;
-    msg.arg2 = id;
-    mHandler.sendMessage(msg);
-  }
-
-  private synchronized void onSetReport(byte reportType, byte reportId,
-                                        byte[] data) {
-    if (DBG)
-      Log.v(TAG, "onSetReport(): reportType=" + reportType + " reportId=" +
-                     reportId);
-
-    ByteBuffer bb = ByteBuffer.wrap(data);
-
-    Message msg = mHandler.obtainMessage(MESSAGE_SET_REPORT);
-    msg.arg1 = reportType;
-    msg.arg2 = reportId;
-    msg.obj = bb;
-    mHandler.sendMessage(msg);
-  }
-
-  private synchronized void onSetProtocol(byte protocol) {
-    if (DBG)
-      Log.v(TAG, "onSetProtocol(): protocol=" + protocol);
-
-    Message msg = mHandler.obtainMessage(MESSAGE_SET_PROTOCOL);
-    msg.arg1 = protocol;
-    mHandler.sendMessage(msg);
-  }
-
-  private synchronized void onIntrData(byte reportId, byte[] data) {
-    if (DBG)
-      Log.v(TAG, "onIntrData(): reportId=" + reportId);
-
-    ByteBuffer bb = ByteBuffer.wrap(data);
-
-    Message msg = mHandler.obtainMessage(MESSAGE_INTR_DATA);
-    msg.arg1 = reportId;
-    msg.obj = bb;
-    mHandler.sendMessage(msg);
-  }
-
-  private synchronized void onVirtualCableUnplug() {
-    if (DBG)
-      Log.v(TAG, "onVirtualCableUnplug()");
-
-    Message msg = mHandler.obtainMessage(MESSAGE_VC_UNPLUG);
-    mHandler.sendMessage(msg);
-  }
-
-  private void broadcastConnectionState(BluetoothDevice device, int newState) {
-    if (DBG)
-      Log.v(TAG, "broadcastConnectionState(): device=" + device.getAddress() +
-                     " newState=" + newState);
-
-    if (mHidDevice != null && !mHidDevice.equals(device)) {
-        Log.w(TAG, "Connection state changed for unknown device, ignoring");
-        return;
-    }
-
-    int prevState = mHidDeviceState;
-    mHidDeviceState = newState;
-
-    Log.i(TAG, "connection state for " + device.getAddress() + ": " +
-                   prevState + " -> " + newState);
-
-    if (prevState == newState) {
-      return;
-    }
-
-    Intent intent =
-        new Intent(BluetoothInputHost.ACTION_CONNECTION_STATE_CHANGED);
-    intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
-    intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
-    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-    sendBroadcast(intent, BLUETOOTH_PERM);
-  }
-
-  private static int convertHalState(int halState) {
-    switch (halState) {
-    case CONN_STATE_CONNECTED:
-      return BluetoothProfile.STATE_CONNECTED;
-    case CONN_STATE_CONNECTING:
-      return BluetoothProfile.STATE_CONNECTING;
-    case CONN_STATE_DISCONNECTED:
-      return BluetoothProfile.STATE_DISCONNECTED;
-    case CONN_STATE_DISCONNECTING:
-      return BluetoothProfile.STATE_DISCONNECTING;
-    default:
-      return BluetoothProfile.STATE_DISCONNECTED;
-    }
-  }
-
-  private final static int CONN_STATE_CONNECTED = 0;
-  private final static int CONN_STATE_CONNECTING = 1;
-  private final static int CONN_STATE_DISCONNECTED = 2;
-  private final static int CONN_STATE_DISCONNECTING = 3;
-
-  private native static void classInitNative();
-  private native void initNative();
-  private native void cleanupNative();
-  private native boolean registerAppNative(String name, String description,
-                                           String provider, byte subclass,
-                                           byte[] descriptors, int[] inQos,
-                                           int[] outQos);
-  private native boolean unregisterAppNative();
-  private native boolean sendReportNative(int id, byte[] data);
-  private native boolean replyReportNative(byte type, byte id, byte[] data);
-  private native boolean unplugNative();
-  private native boolean connectNative(byte[] btAddress);
-  private native boolean disconnectNative();
-  private native boolean reportErrorNative(byte error);
-}
diff --git a/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
new file mode 100644
index 0000000..c0f7042
--- /dev/null
+++ b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2017 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 the native inteface that is used by HID Device service to
+ * send or receive messages from the native stack. This file is registered
+ * for the native methods in the corresponding JNI C++ file.
+ */
+
+package com.android.bluetooth.hid;
+
+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;
+
+/**
+ * HID Device Native Interface to/from JNI.
+ */
+public class HidDeviceNativeInterface {
+    private static final String TAG = "HidDeviceNativeInterface";
+    private BluetoothAdapter mAdapter;
+
+    @GuardedBy("INSTANCE_LOCK")
+    private static HidDeviceNativeInterface sInstance;
+    private static final Object INSTANCE_LOCK = new Object();
+
+    static {
+        classInitNative();
+    }
+
+    @VisibleForTesting
+    private HidDeviceNativeInterface() {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mAdapter == null) {
+            Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+        }
+    }
+
+    /**
+     * Get the singleton instance.
+     */
+    public static HidDeviceNativeInterface getInstance() {
+        synchronized (INSTANCE_LOCK) {
+            if (sInstance == null) {
+                setInstance(new HidDeviceNativeInterface());
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Set the singleton instance.
+     *
+     * @param nativeInterface native interface
+     */
+    private static void setInstance(HidDeviceNativeInterface nativeInterface) {
+        sInstance = nativeInterface;
+    }
+
+    /**
+     * Initializes the native interface.
+     */
+    public void init() {
+        initNative();
+    }
+
+    /**
+     * Cleanup the native interface.
+     */
+    public void cleanup() {
+        cleanupNative();
+    }
+
+    /**
+     * Registers the application
+     *
+     * @param name name of the HID Device application
+     * @param description description of the HID Device application
+     * @param provider provider of the HID Device application
+     * @param subclass subclass of the HID Device application
+     * @param descriptors HID descriptors
+     * @param inQos incoming QoS settings
+     * @param outQos outgoing QoS settings
+     * @return the result of the native call
+     */
+    public boolean registerApp(String name, String description, String provider,
+            byte subclass, byte[] descriptors, int[] inQos, int[] outQos) {
+        return registerAppNative(name, description, provider, subclass, descriptors, inQos, outQos);
+    }
+
+    /**
+     * Unregisters the application
+     *
+     * @return the result of the native call
+     */
+    public boolean unregisterApp() {
+        return unregisterAppNative();
+    }
+
+    /**
+     * Send report to the remote host
+     *
+     * @param id report ID
+     * @param data report data array
+     * @return the result of the native call
+     */
+    public boolean sendReport(int id, byte[] data) {
+        return sendReportNative(id, data);
+    }
+
+    /**
+     * Reply report to the remote host
+     *
+     * @param type report type
+     * @param id report ID
+     * @param data report data array
+     * @return the result of the native call
+     */
+    public boolean replyReport(byte type, byte id, byte[] data) {
+        return replyReportNative(type, id, data);
+    }
+
+    /**
+     * Send virtual unplug to the remote host
+     *
+     * @return the result of the native call
+     */
+    public boolean unplug() {
+        return unplugNative();
+    }
+
+    /**
+     * Connect to the remote host
+     *
+     * @param device remote host device
+     * @return the result of the native call
+     */
+    public boolean connect(BluetoothDevice device) {
+        return connectNative(getByteAddress(device));
+    }
+
+    /**
+     * Disconnect from the remote host
+     *
+     * @return the result of the native call
+     */
+    public boolean disconnect() {
+        return disconnectNative();
+    }
+
+    /**
+     * Report error to the remote host
+     *
+     * @param error error byte
+     * @return the result of the native call
+     */
+    public boolean reportError(byte error) {
+        return reportErrorNative(error);
+    }
+
+    private synchronized void onApplicationStateChanged(byte[] address, boolean registered) {
+        HidDeviceService service = HidDeviceService.getHidDeviceService();
+        if (service != null) {
+            service.onApplicationStateChangedFromNative(getDevice(address), registered);
+        } else {
+            Log.wtfStack(TAG, "FATAL: onApplicationStateChanged() "
+                    + "is called from the stack while service is not available.");
+        }
+    }
+
+    private synchronized void onConnectStateChanged(byte[] address, int state) {
+        HidDeviceService service = HidDeviceService.getHidDeviceService();
+        if (service != null) {
+            service.onConnectStateChangedFromNative(getDevice(address), state);
+        } else {
+            Log.wtfStack(TAG, "FATAL: onConnectStateChanged() "
+                    + "is called from the stack while service is not available.");
+        }
+    }
+
+    private synchronized void onGetReport(byte type, byte id, short bufferSize) {
+        HidDeviceService service = HidDeviceService.getHidDeviceService();
+        if (service != null) {
+            service.onGetReportFromNative(type, id, bufferSize);
+        } else {
+            Log.wtfStack(TAG, "FATAL: onGetReport() "
+                    + "is called from the stack while service is not available.");
+        }
+    }
+
+    private synchronized void onSetReport(byte reportType, byte reportId, byte[] data) {
+        HidDeviceService service = HidDeviceService.getHidDeviceService();
+        if (service != null) {
+            service.onSetReportFromNative(reportType, reportId, data);
+        } else {
+            Log.wtfStack(TAG, "FATAL: onSetReport() "
+                    + "is called from the stack while service is not available.");
+        }
+    }
+
+    private synchronized void onSetProtocol(byte protocol) {
+        HidDeviceService service = HidDeviceService.getHidDeviceService();
+        if (service != null) {
+            service.onSetProtocolFromNative(protocol);
+        } else {
+            Log.wtfStack(TAG, "FATAL: onSetProtocol() "
+                    + "is called from the stack while service is not available.");
+        }
+    }
+
+    private synchronized void onInterruptData(byte reportId, byte[] data) {
+        HidDeviceService service = HidDeviceService.getHidDeviceService();
+        if (service != null) {
+            service.onInterruptDataFromNative(reportId, data);
+        } else {
+            Log.wtfStack(TAG, "FATAL: onInterruptData() "
+                    + "is called from the stack while service is not available.");
+        }
+    }
+
+    private synchronized void onVirtualCableUnplug() {
+        HidDeviceService service = HidDeviceService.getHidDeviceService();
+        if (service != null) {
+            service.onVirtualCableUnplugFromNative();
+        } else {
+            Log.wtfStack(TAG, "FATAL: onVirtualCableUnplug() "
+                    + "is called from the stack while service is not available.");
+        }
+    }
+
+    private BluetoothDevice getDevice(byte[] address) {
+        if (address == null) {
+            return null;
+        }
+        return mAdapter.getRemoteDevice(address);
+    }
+
+    private byte[] getByteAddress(BluetoothDevice device) {
+        return Utils.getBytesFromAddress(device.getAddress());
+    }
+
+    private static native void classInitNative();
+
+    private native void initNative();
+
+    private native void cleanupNative();
+
+    private native boolean registerAppNative(String name, String description, String provider,
+            byte subclass, byte[] descriptors, int[] inQos, int[] outQos);
+
+    private native boolean unregisterAppNative();
+
+    private native boolean sendReportNative(int id, byte[] data);
+
+    private native boolean replyReportNative(byte type, byte id, byte[] data);
+
+    private native boolean unplugNative();
+
+    private native boolean connectNative(byte[] btAddress);
+
+    private native boolean disconnectNative();
+
+    private native boolean reportErrorNative(byte error);
+}
diff --git a/src/com/android/bluetooth/hid/HidDeviceService.java b/src/com/android/bluetooth/hid/HidDeviceService.java
new file mode 100644
index 0000000..eb0de8e
--- /dev/null
+++ b/src/com/android/bluetooth/hid/HidDeviceService.java
@@ -0,0 +1,859 @@
+/*
+ * 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.hid;
+
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidDevice;
+import android.bluetooth.BluetoothHidDeviceAppQosSettings;
+import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHidDevice;
+import android.bluetooth.IBluetoothHidDeviceCallback;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+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 com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/** @hide */
+public class HidDeviceService extends ProfileService {
+    private static final boolean DBG = false;
+    private static final String TAG = HidDeviceService.class.getSimpleName();
+
+    private static final int MESSAGE_APPLICATION_STATE_CHANGED = 1;
+    private static final int MESSAGE_CONNECT_STATE_CHANGED = 2;
+    private static final int MESSAGE_GET_REPORT = 3;
+    private static final int MESSAGE_SET_REPORT = 4;
+    private static final int MESSAGE_SET_PROTOCOL = 5;
+    private static final int MESSAGE_INTR_DATA = 6;
+    private static final int MESSAGE_VC_UNPLUG = 7;
+    private static final int MESSAGE_IMPORTANCE_CHANGE = 8;
+
+    private static final int FOREGROUND_IMPORTANCE_CUTOFF =
+            ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+
+    private static HidDeviceService sHidDeviceService;
+
+    private HidDeviceNativeInterface mHidDeviceNativeInterface;
+
+    private boolean mNativeAvailable = false;
+    private BluetoothDevice mHidDevice;
+    private int mHidDeviceState = BluetoothHidDevice.STATE_DISCONNECTED;
+    private int mUserUid = 0;
+    private IBluetoothHidDeviceCallback mCallback;
+    private BluetoothHidDeviceDeathRecipient mDeathRcpt;
+    private ActivityManager mActivityManager;
+
+    private HidDeviceServiceHandler mHandler;
+
+    private class HidDeviceServiceHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            if (DBG) {
+                Log.d(TAG, "handleMessage(): msg.what=" + msg.what);
+            }
+
+            switch (msg.what) {
+                case MESSAGE_APPLICATION_STATE_CHANGED: {
+                    BluetoothDevice device = msg.obj != null ? (BluetoothDevice) msg.obj : null;
+                    boolean success = (msg.arg1 != 0);
+
+                    if (success) {
+                        Log.d(TAG, "App registered, set device to: " + device);
+                        mHidDevice = device;
+                    } else {
+                        mHidDevice = null;
+                    }
+
+                    try {
+                        if (mCallback != null) {
+                            mCallback.onAppStatusChanged(device, success);
+                        } else {
+                            break;
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "e=" + e.toString());
+                        e.printStackTrace();
+                    }
+
+                    if (success) {
+                        mDeathRcpt = new BluetoothHidDeviceDeathRecipient(HidDeviceService.this);
+                        if (mCallback != null) {
+                            IBinder binder = mCallback.asBinder();
+                            try {
+                                binder.linkToDeath(mDeathRcpt, 0);
+                                Log.i(TAG, "IBinder.linkToDeath() ok");
+                            } catch (RemoteException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                    } else if (mDeathRcpt != null) {
+                        if (mCallback != null) {
+                            IBinder binder = mCallback.asBinder();
+                            try {
+                                binder.unlinkToDeath(mDeathRcpt, 0);
+                                Log.i(TAG, "IBinder.unlinkToDeath() ok");
+                            } catch (NoSuchElementException e) {
+                                e.printStackTrace();
+                            }
+                            mDeathRcpt.cleanup();
+                            mDeathRcpt = null;
+                        }
+                    }
+
+                    if (!success) {
+                        mCallback = null;
+                    }
+
+                    break;
+                }
+
+                case MESSAGE_CONNECT_STATE_CHANGED: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    int halState = msg.arg1;
+                    int state = convertHalState(halState);
+
+                    if (state != BluetoothHidDevice.STATE_DISCONNECTED) {
+                        mHidDevice = device;
+                    }
+
+                    setAndBroadcastConnectionState(device, state);
+
+                    try {
+                        if (mCallback != null) {
+                            mCallback.onConnectionStateChanged(device, state);
+                        }
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                    break;
+                }
+
+                case MESSAGE_GET_REPORT:
+                    byte type = (byte) msg.arg1;
+                    byte id = (byte) msg.arg2;
+                    int bufferSize = msg.obj == null ? 0 : ((Integer) msg.obj).intValue();
+
+                    try {
+                        if (mCallback != null) {
+                            mCallback.onGetReport(mHidDevice, type, id, bufferSize);
+                        }
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                    break;
+
+                case MESSAGE_SET_REPORT: {
+                    byte reportType = (byte) msg.arg1;
+                    byte reportId = (byte) msg.arg2;
+                    byte[] data = ((ByteBuffer) msg.obj).array();
+
+                    try {
+                        if (mCallback != null) {
+                            mCallback.onSetReport(mHidDevice, reportType, reportId, data);
+                        }
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                    break;
+                }
+
+                case MESSAGE_SET_PROTOCOL:
+                    byte protocol = (byte) msg.arg1;
+
+                    try {
+                        if (mCallback != null) {
+                            mCallback.onSetProtocol(mHidDevice, protocol);
+                        }
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                    break;
+
+                case MESSAGE_INTR_DATA:
+                    byte reportId = (byte) msg.arg1;
+                    byte[] data = ((ByteBuffer) msg.obj).array();
+
+                    try {
+                        if (mCallback != null) {
+                            mCallback.onInterruptData(mHidDevice, reportId, data);
+                        }
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                    break;
+
+                case MESSAGE_VC_UNPLUG:
+                    try {
+                        if (mCallback != null) {
+                            mCallback.onVirtualCableUnplug(mHidDevice);
+                        }
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                    mHidDevice = null;
+                    break;
+
+                case MESSAGE_IMPORTANCE_CHANGE:
+                    int importance = msg.arg1;
+                    int uid = msg.arg2;
+                    if (importance > FOREGROUND_IMPORTANCE_CUTOFF
+                            && uid >= Process.FIRST_APPLICATION_UID) {
+                        unregisterAppUid(uid);
+                    }
+                    break;
+            }
+        }
+    }
+
+    private static class BluetoothHidDeviceDeathRecipient implements IBinder.DeathRecipient {
+        private HidDeviceService mService;
+
+        BluetoothHidDeviceDeathRecipient(HidDeviceService service) {
+            mService = service;
+        }
+
+        @Override
+        public void binderDied() {
+            Log.w(TAG, "Binder died, need to unregister app :(");
+            mService.unregisterApp();
+        }
+
+        public void cleanup() {
+            mService = null;
+        }
+    }
+
+    private ActivityManager.OnUidImportanceListener mUidImportanceListener =
+            new ActivityManager.OnUidImportanceListener() {
+                @Override
+                public void onUidImportance(final int uid, final int importance) {
+                    Message message = mHandler.obtainMessage(MESSAGE_IMPORTANCE_CHANGE);
+                    message.arg1 = importance;
+                    message.arg2 = uid;
+                    mHandler.sendMessage(message);
+                }
+            };
+
+    @VisibleForTesting
+    static class BluetoothHidDeviceBinder extends IBluetoothHidDevice.Stub
+            implements IProfileServiceBinder {
+
+        private static final String TAG = BluetoothHidDeviceBinder.class.getSimpleName();
+
+        private HidDeviceService mService;
+
+        BluetoothHidDeviceBinder(HidDeviceService service) {
+            mService = service;
+        }
+
+        @VisibleForTesting
+        HidDeviceService getServiceForTesting() {
+            if (mService != null && mService.isAvailable()) {
+                return mService;
+            }
+            return null;
+        }
+
+        @Override
+        public void cleanup() {
+            mService = null;
+        }
+
+        private HidDeviceService getService() {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "HidDevice call not allowed for non-active user");
+                return null;
+            }
+
+            if (mService != null && mService.isAvailable()) {
+                return mService;
+            }
+
+            return null;
+        }
+
+        @Override
+        public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
+                BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
+                IBluetoothHidDeviceCallback callback) {
+            if (DBG) {
+                Log.d(TAG, "registerApp()");
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return false;
+            }
+
+            return service.registerApp(sdp, inQos, outQos, callback);
+        }
+
+        @Override
+        public boolean unregisterApp() {
+            if (DBG) {
+                Log.d(TAG, "unregisterApp()");
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return false;
+            }
+
+            return service.unregisterApp();
+        }
+
+        @Override
+        public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
+            if (DBG) {
+                Log.d(TAG, "sendReport(): device=" + device + "  id=" + id);
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return false;
+            }
+
+            return service.sendReport(device, id, data);
+        }
+
+        @Override
+        public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+            if (DBG) {
+                Log.d(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return false;
+            }
+
+            return service.replyReport(device, type, id, data);
+        }
+
+        @Override
+        public boolean unplug(BluetoothDevice device) {
+            if (DBG) {
+                Log.d(TAG, "unplug(): device=" + device);
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return false;
+            }
+
+            return service.unplug(device);
+        }
+
+        @Override
+        public boolean connect(BluetoothDevice device) {
+            if (DBG) {
+                Log.d(TAG, "connect(): device=" + device);
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return false;
+            }
+
+            return service.connect(device);
+        }
+
+        @Override
+        public boolean disconnect(BluetoothDevice device) {
+            if (DBG) {
+                Log.d(TAG, "disconnect(): device=" + device);
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return false;
+            }
+
+            return service.disconnect(device);
+        }
+
+        @Override
+        public boolean reportError(BluetoothDevice device, byte error) {
+            if (DBG) {
+                Log.d(TAG, "reportError(): device=" + device + " error=" + error);
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return false;
+            }
+
+            return service.reportError(device, error);
+        }
+
+        @Override
+        public int getConnectionState(BluetoothDevice device) {
+            if (DBG) {
+                Log.d(TAG, "getConnectionState(): device=" + device);
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return BluetoothHidDevice.STATE_DISCONNECTED;
+            }
+
+            return service.getConnectionState(device);
+        }
+
+        @Override
+        public List<BluetoothDevice> getConnectedDevices() {
+            if (DBG) {
+                Log.d(TAG, "getConnectedDevices()");
+            }
+
+            return getDevicesMatchingConnectionStates(new int[]{BluetoothProfile.STATE_CONNECTED});
+        }
+
+        @Override
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+            if (DBG) {
+                Log.d(TAG,
+                        "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
+
+            return service.getDevicesMatchingConnectionStates(states);
+        }
+
+        @Override
+        public String getUserAppName() {
+            HidDeviceService service = getService();
+            if (service == null) {
+                return "";
+            }
+            return service.getUserAppName();
+        }
+    }
+
+    @Override
+    protected IProfileServiceBinder initBinder() {
+        return new BluetoothHidDeviceBinder(this);
+    }
+
+    private boolean checkDevice(BluetoothDevice device) {
+        if (mHidDevice == null || !mHidDevice.equals(device)) {
+            Log.w(TAG, "Unknown device: " + device);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean checkCallingUid() {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != mUserUid) {
+            Log.w(TAG, "checkCallingUid(): caller UID doesn't match registered user UID");
+            return false;
+        }
+        return true;
+    }
+
+    synchronized boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
+            BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
+            IBluetoothHidDeviceCallback callback) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (mUserUid != 0) {
+            Log.w(TAG, "registerApp(): failed because another app is registered");
+            return false;
+        }
+
+        int callingUid = Binder.getCallingUid();
+        if (DBG) {
+            Log.d(TAG, "registerApp(): calling uid=" + callingUid);
+        }
+        if (callingUid >= Process.FIRST_APPLICATION_UID
+                && mActivityManager.getUidImportance(callingUid) > FOREGROUND_IMPORTANCE_CUTOFF) {
+            Log.w(TAG, "registerApp(): failed because the app is not foreground");
+            return false;
+        }
+        mUserUid = callingUid;
+        mCallback = callback;
+
+        return mHidDeviceNativeInterface.registerApp(
+                sdp.getName(),
+                sdp.getDescription(),
+                sdp.getProvider(),
+                sdp.getSubclass(),
+                sdp.getDescriptors(),
+                inQos == null
+                        ? null
+                        : new int[] {
+                            inQos.getServiceType(),
+                            inQos.getTokenRate(),
+                            inQos.getTokenBucketSize(),
+                            inQos.getPeakBandwidth(),
+                            inQos.getLatency(),
+                            inQos.getDelayVariation()
+                        },
+                outQos == null
+                        ? null
+                        : new int[] {
+                            outQos.getServiceType(),
+                            outQos.getTokenRate(),
+                            outQos.getTokenBucketSize(),
+                            outQos.getPeakBandwidth(),
+                            outQos.getLatency(),
+                            outQos.getDelayVariation()
+                        });
+    }
+
+    synchronized boolean unregisterApp() {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "unregisterApp()");
+        }
+
+        int callingUid = Binder.getCallingUid();
+        return unregisterAppUid(callingUid);
+    }
+
+    private synchronized boolean unregisterAppUid(int uid) {
+        if (DBG) {
+            Log.d(TAG, "unregisterAppUid(): uid=" + uid);
+        }
+
+        if (mUserUid != 0 && (uid == mUserUid || uid < Process.FIRST_APPLICATION_UID)) {
+            mUserUid = 0;
+            return mHidDeviceNativeInterface.unregisterApp();
+        }
+        if (DBG) {
+            Log.d(TAG, "unregisterAppUid(): caller UID doesn't match user UID");
+        }
+        return false;
+    }
+
+    synchronized boolean sendReport(BluetoothDevice device, int id, byte[] data) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "sendReport(): device=" + device + " id=" + id);
+        }
+
+        return checkDevice(device) && checkCallingUid()
+                && mHidDeviceNativeInterface.sendReport(id, data);
+    }
+
+    synchronized boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
+        }
+
+        return checkDevice(device) && checkCallingUid()
+                && mHidDeviceNativeInterface.replyReport(type, id, data);
+    }
+
+    synchronized boolean unplug(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "unplug(): device=" + device);
+        }
+
+        return checkDevice(device) && checkCallingUid()
+                && mHidDeviceNativeInterface.unplug();
+    }
+
+    synchronized boolean connect(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "connect(): device=" + device);
+        }
+
+        return checkCallingUid() && mHidDeviceNativeInterface.connect(device);
+    }
+
+    synchronized boolean disconnect(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "disconnect(): device=" + device);
+        }
+
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != mUserUid && callingUid >= Process.FIRST_APPLICATION_UID) {
+            Log.w(TAG, "disconnect(): caller UID doesn't match user UID");
+            return false;
+        }
+        return checkDevice(device) && mHidDeviceNativeInterface.disconnect();
+    }
+
+    synchronized boolean reportError(BluetoothDevice device, byte error) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "reportError(): device=" + device + " error=" + error);
+        }
+
+        return checkDevice(device) && checkCallingUid()
+                && mHidDeviceNativeInterface.reportError(error);
+    }
+
+    synchronized String getUserAppName() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (mUserUid < Process.FIRST_APPLICATION_UID) {
+            return "";
+        }
+        String appName = getPackageManager().getNameForUid(mUserUid);
+        return appName != null ? appName : "";
+    }
+
+    @Override
+    protected boolean start() {
+        if (DBG) {
+            Log.d(TAG, "start()");
+        }
+
+        mHandler = new HidDeviceServiceHandler();
+        mHidDeviceNativeInterface = HidDeviceNativeInterface.getInstance();
+        mHidDeviceNativeInterface.init();
+        mNativeAvailable = true;
+        mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+        mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
+                FOREGROUND_IMPORTANCE_CUTOFF);
+        setHidDeviceService(this);
+        return true;
+    }
+
+    @Override
+    protected boolean stop() {
+        if (DBG) {
+            Log.d(TAG, "stop()");
+        }
+
+        if (sHidDeviceService == null) {
+            Log.w(TAG, "stop() called before start()");
+            return true;
+        }
+
+        setHidDeviceService(null);
+        if (mNativeAvailable) {
+            mHidDeviceNativeInterface.cleanup();
+            mNativeAvailable = false;
+        }
+        mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
+        return true;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        Log.d(TAG, "Need to unregister app");
+        unregisterApp();
+        return super.onUnbind(intent);
+    }
+
+    /**
+     * Get the HID Device Service instance
+     * @return HID Device Service instance
+     */
+    public static synchronized HidDeviceService getHidDeviceService() {
+        if (sHidDeviceService == null) {
+            Log.d(TAG, "getHidDeviceService(): service is NULL");
+            return null;
+        }
+        if (!sHidDeviceService.isAvailable()) {
+            Log.d(TAG, "getHidDeviceService(): service is not available");
+            return null;
+        }
+        return sHidDeviceService;
+    }
+
+    private static synchronized void setHidDeviceService(HidDeviceService instance) {
+        if (DBG) {
+            Log.d(TAG, "setHidDeviceService(): set to: " + instance);
+        }
+        sHidDeviceService = instance;
+    }
+
+    int getConnectionState(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (mHidDevice != null && mHidDevice.equals(device)) {
+            return mHidDeviceState;
+        }
+        return BluetoothHidDevice.STATE_DISCONNECTED;
+    }
+
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
+
+        if (mHidDevice != null) {
+            for (int state : states) {
+                if (state == mHidDeviceState) {
+                    inputDevices.add(mHidDevice);
+                    break;
+                }
+            }
+        }
+        return inputDevices;
+    }
+
+    synchronized void onApplicationStateChangedFromNative(BluetoothDevice device,
+            boolean registered) {
+        if (DBG) {
+            Log.d(TAG, "onApplicationStateChanged(): registered=" + registered);
+        }
+
+        Message msg = mHandler.obtainMessage(MESSAGE_APPLICATION_STATE_CHANGED);
+        msg.obj = device;
+        msg.arg1 = registered ? 1 : 0;
+        mHandler.sendMessage(msg);
+    }
+
+    synchronized void onConnectStateChangedFromNative(BluetoothDevice device, int state) {
+        if (DBG) {
+            Log.d(TAG, "onConnectStateChanged(): device="
+                    + device + " state=" + state);
+        }
+
+        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
+        msg.obj = device;
+        msg.arg1 = state;
+        mHandler.sendMessage(msg);
+    }
+
+    synchronized void onGetReportFromNative(byte type, byte id, short bufferSize) {
+        if (DBG) {
+            Log.d(TAG, "onGetReport(): type=" + type + " id=" + id + " bufferSize=" + bufferSize);
+        }
+
+        Message msg = mHandler.obtainMessage(MESSAGE_GET_REPORT);
+        msg.obj = bufferSize > 0 ? new Integer(bufferSize) : null;
+        msg.arg1 = type;
+        msg.arg2 = id;
+        mHandler.sendMessage(msg);
+    }
+
+    synchronized void onSetReportFromNative(byte reportType, byte reportId, byte[] data) {
+        if (DBG) {
+            Log.d(TAG, "onSetReport(): reportType=" + reportType + " reportId=" + reportId);
+        }
+
+        ByteBuffer bb = ByteBuffer.wrap(data);
+
+        Message msg = mHandler.obtainMessage(MESSAGE_SET_REPORT);
+        msg.arg1 = reportType;
+        msg.arg2 = reportId;
+        msg.obj = bb;
+        mHandler.sendMessage(msg);
+    }
+
+    synchronized void onSetProtocolFromNative(byte protocol) {
+        if (DBG) {
+            Log.d(TAG, "onSetProtocol(): protocol=" + protocol);
+        }
+
+        Message msg = mHandler.obtainMessage(MESSAGE_SET_PROTOCOL);
+        msg.arg1 = protocol;
+        mHandler.sendMessage(msg);
+    }
+
+    synchronized void onInterruptDataFromNative(byte reportId, byte[] data) {
+        if (DBG) {
+            Log.d(TAG, "onInterruptData(): reportId=" + reportId);
+        }
+
+        ByteBuffer bb = ByteBuffer.wrap(data);
+
+        Message msg = mHandler.obtainMessage(MESSAGE_INTR_DATA);
+        msg.arg1 = reportId;
+        msg.obj = bb;
+        mHandler.sendMessage(msg);
+    }
+
+    synchronized void onVirtualCableUnplugFromNative() {
+        if (DBG) {
+            Log.d(TAG, "onVirtualCableUnplug()");
+        }
+
+        Message msg = mHandler.obtainMessage(MESSAGE_VC_UNPLUG);
+        mHandler.sendMessage(msg);
+    }
+
+    private void setAndBroadcastConnectionState(BluetoothDevice device, int newState) {
+        if (DBG) {
+            Log.d(TAG, "setAndBroadcastConnectionState(): device=" + device.getAddress()
+                    + " oldState=" + mHidDeviceState + " newState=" + newState);
+        }
+
+        if (mHidDevice != null && !mHidDevice.equals(device)) {
+            Log.w(TAG, "Connection state changed for unknown device, ignoring");
+            return;
+        }
+
+        int prevState = mHidDeviceState;
+        mHidDeviceState = newState;
+
+        if (prevState == newState) {
+            Log.w(TAG, "Connection state is unchanged, ignoring");
+            return;
+        }
+
+        if (newState == BluetoothProfile.STATE_CONNECTED) {
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HID_DEVICE);
+        }
+
+        Intent intent = new Intent(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+
+    private static int convertHalState(int halState) {
+        switch (halState) {
+            case HAL_CONN_STATE_CONNECTED:
+                return BluetoothProfile.STATE_CONNECTED;
+            case HAL_CONN_STATE_CONNECTING:
+                return BluetoothProfile.STATE_CONNECTING;
+            case HAL_CONN_STATE_DISCONNECTED:
+                return BluetoothProfile.STATE_DISCONNECTED;
+            case HAL_CONN_STATE_DISCONNECTING:
+                return BluetoothProfile.STATE_DISCONNECTING;
+            default:
+                return BluetoothProfile.STATE_DISCONNECTED;
+        }
+    }
+
+    static final int HAL_CONN_STATE_CONNECTED = 0;
+    static final int HAL_CONN_STATE_CONNECTING = 1;
+    static final int HAL_CONN_STATE_DISCONNECTED = 2;
+    static final int HAL_CONN_STATE_DISCONNECTING = 3;
+}
diff --git a/src/com/android/bluetooth/hid/HidHostService.java b/src/com/android/bluetooth/hid/HidHostService.java
new file mode 100644
index 0000000..63f5206
--- /dev/null
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -0,0 +1,894 @@
+/*
+ * 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.hid;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidHost;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHidHost;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
+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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides Bluetooth Hid Host profile, as a service in
+ * the Bluetooth application.
+ * @hide
+ */
+public class HidHostService extends ProfileService {
+    private static final boolean DBG = false;
+    private static final String TAG = "BluetoothHidHostService";
+
+    private Map<BluetoothDevice, Integer> mInputDevices;
+    private boolean mNativeAvailable;
+    private static HidHostService sHidHostService;
+    private BluetoothDevice mTargetDevice = null;
+
+    private static final int MESSAGE_CONNECT = 1;
+    private static final int MESSAGE_DISCONNECT = 2;
+    private static final int MESSAGE_CONNECT_STATE_CHANGED = 3;
+    private static final int MESSAGE_GET_PROTOCOL_MODE = 4;
+    private static final int MESSAGE_VIRTUAL_UNPLUG = 5;
+    private static final int MESSAGE_ON_GET_PROTOCOL_MODE = 6;
+    private static final int MESSAGE_SET_PROTOCOL_MODE = 7;
+    private static final int MESSAGE_GET_REPORT = 8;
+    private static final int MESSAGE_ON_GET_REPORT = 9;
+    private static final int MESSAGE_SET_REPORT = 10;
+    private static final int MESSAGE_SEND_DATA = 11;
+    private static final int MESSAGE_ON_VIRTUAL_UNPLUG = 12;
+    private static final int MESSAGE_ON_HANDSHAKE = 13;
+    private static final int MESSAGE_GET_IDLE_TIME = 14;
+    private static final int MESSAGE_ON_GET_IDLE_TIME = 15;
+    private static final int MESSAGE_SET_IDLE_TIME = 16;
+
+    static {
+        classInitNative();
+    }
+
+    @Override
+    public IProfileServiceBinder initBinder() {
+        return new BluetoothHidHostBinder(this);
+    }
+
+    @Override
+    protected boolean start() {
+        mInputDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>());
+        initializeNative();
+        mNativeAvailable = true;
+        setHidHostService(this);
+        return true;
+    }
+
+    @Override
+    protected boolean stop() {
+        if (DBG) {
+            Log.d(TAG, "Stopping Bluetooth HidHostService");
+        }
+        return true;
+    }
+
+    @Override
+    protected void cleanup() {
+        if (DBG) Log.d(TAG, "Stopping Bluetooth HidHostService");
+        if (mNativeAvailable) {
+            cleanupNative();
+            mNativeAvailable = false;
+        }
+
+        if (mInputDevices != null) {
+            for (BluetoothDevice device : mInputDevices.keySet()) {
+                int inputDeviceState = getConnectionState(device);
+                if (inputDeviceState != BluetoothProfile.STATE_DISCONNECTED) {
+                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
+                }
+            }
+            mInputDevices.clear();
+        }
+        // TODO(b/72948646): this should be moved to stop()
+        setHidHostService(null);
+    }
+
+    public static synchronized HidHostService getHidHostService() {
+        if (sHidHostService == null) {
+            Log.w(TAG, "getHidHostService(): service is null");
+            return null;
+        }
+        if (!sHidHostService.isAvailable()) {
+            Log.w(TAG, "getHidHostService(): service is not available ");
+            return null;
+        }
+        return sHidHostService;
+    }
+
+    private static synchronized void setHidHostService(HidHostService instance) {
+        if (DBG) {
+            Log.d(TAG, "setHidHostService(): set to: " + instance);
+        }
+        sHidHostService = instance;
+    }
+
+    private final Handler mHandler = new Handler() {
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (DBG) Log.v(TAG, "handleMessage(): msg.what=" + msg.what);
+
+            switch (msg.what) {
+                case MESSAGE_CONNECT: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    if (!connectHidNative(Utils.getByteAddress(device))) {
+                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING);
+                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
+                        break;
+                    }
+                    mTargetDevice = device;
+                }
+                break;
+                case MESSAGE_DISCONNECT: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    if (!disconnectHidNative(Utils.getByteAddress(device))) {
+                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING);
+                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
+                        break;
+                    }
+                }
+                break;
+                case MESSAGE_CONNECT_STATE_CHANGED: {
+                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    int halState = msg.arg1;
+                    Integer prevStateInteger = mInputDevices.get(device);
+                    int prevState =
+                            (prevStateInteger == null) ? BluetoothHidHost.STATE_DISCONNECTED
+                                    : prevStateInteger;
+                    if (DBG) {
+                        Log.d(TAG, "MESSAGE_CONNECT_STATE_CHANGED newState:" + convertHalState(
+                                halState) + ", prevState:" + prevState);
+                    }
+                    if (halState == CONN_STATE_CONNECTED
+                            && prevState == BluetoothHidHost.STATE_DISCONNECTED
+                            && (!okToConnect(device))) {
+                        if (DBG) {
+                            Log.d(TAG, "Incoming HID connection rejected");
+                        }
+                        disconnectHidNative(Utils.getByteAddress(device));
+                    } else {
+                        broadcastConnectionState(device, convertHalState(halState));
+                    }
+                    if (halState == CONN_STATE_CONNECTED && (mTargetDevice != null
+                            && mTargetDevice.equals(device))) {
+                        mTargetDevice = null;
+                        // local device originated connection to hid device, move out
+                        // of quiet mode
+                        AdapterService adapterService = AdapterService.getAdapterService();
+                        adapterService.enable(false);
+                    }
+                }
+                break;
+                case MESSAGE_GET_PROTOCOL_MODE: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    if (!getProtocolModeNative(Utils.getByteAddress(device))) {
+                        Log.e(TAG, "Error: get protocol mode native returns false");
+                    }
+                }
+                break;
+
+                case MESSAGE_ON_GET_PROTOCOL_MODE: {
+                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    int protocolMode = msg.arg1;
+                    broadcastProtocolMode(device, protocolMode);
+                }
+                break;
+                case MESSAGE_VIRTUAL_UNPLUG: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    if (!virtualUnPlugNative(Utils.getByteAddress(device))) {
+                        Log.e(TAG, "Error: virtual unplug native returns false");
+                    }
+                }
+                break;
+                case MESSAGE_SET_PROTOCOL_MODE: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    byte protocolMode = (byte) msg.arg1;
+                    Log.d(TAG, "sending set protocol mode(" + protocolMode + ")");
+                    if (!setProtocolModeNative(Utils.getByteAddress(device), protocolMode)) {
+                        Log.e(TAG, "Error: set protocol mode native returns false");
+                    }
+                }
+                break;
+                case MESSAGE_GET_REPORT: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Bundle data = msg.getData();
+                    byte reportType = data.getByte(BluetoothHidHost.EXTRA_REPORT_TYPE);
+                    byte reportId = data.getByte(BluetoothHidHost.EXTRA_REPORT_ID);
+                    int bufferSize = data.getInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE);
+                    if (!getReportNative(Utils.getByteAddress(device), reportType, reportId,
+                            bufferSize)) {
+                        Log.e(TAG, "Error: get report native returns false");
+                    }
+                }
+                break;
+                case MESSAGE_ON_GET_REPORT: {
+                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    Bundle data = msg.getData();
+                    byte[] report = data.getByteArray(BluetoothHidHost.EXTRA_REPORT);
+                    int bufferSize = data.getInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE);
+                    broadcastReport(device, report, bufferSize);
+                }
+                break;
+                case MESSAGE_ON_HANDSHAKE: {
+                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    int status = msg.arg1;
+                    broadcastHandshake(device, status);
+                }
+                break;
+                case MESSAGE_SET_REPORT: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Bundle data = msg.getData();
+                    byte reportType = data.getByte(BluetoothHidHost.EXTRA_REPORT_TYPE);
+                    String report = data.getString(BluetoothHidHost.EXTRA_REPORT);
+                    if (!setReportNative(Utils.getByteAddress(device), reportType, report)) {
+                        Log.e(TAG, "Error: set report native returns false");
+                    }
+                }
+                break;
+                case MESSAGE_SEND_DATA: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Bundle data = msg.getData();
+                    String report = data.getString(BluetoothHidHost.EXTRA_REPORT);
+                    if (!sendDataNative(Utils.getByteAddress(device), report)) {
+                        Log.e(TAG, "Error: send data native returns false");
+                    }
+                }
+                break;
+                case MESSAGE_ON_VIRTUAL_UNPLUG: {
+                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    int status = msg.arg1;
+                    broadcastVirtualUnplugStatus(device, status);
+                }
+                break;
+                case MESSAGE_GET_IDLE_TIME: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    if (!getIdleTimeNative(Utils.getByteAddress(device))) {
+                        Log.e(TAG, "Error: get idle time native returns false");
+                    }
+                }
+                break;
+                case MESSAGE_ON_GET_IDLE_TIME: {
+                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    int idleTime = msg.arg1;
+                    broadcastIdleTime(device, idleTime);
+                }
+                break;
+                case MESSAGE_SET_IDLE_TIME: {
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Bundle data = msg.getData();
+                    byte idleTime = data.getByte(BluetoothHidHost.EXTRA_IDLE_TIME);
+                    if (!setIdleTimeNative(Utils.getByteAddress(device), idleTime)) {
+                        Log.e(TAG, "Error: get idle time native returns false");
+                    }
+                }
+                break;
+            }
+        }
+    };
+
+    /**
+     * Handlers for incoming service calls
+     */
+    private static class BluetoothHidHostBinder extends IBluetoothHidHost.Stub
+            implements IProfileServiceBinder {
+        private HidHostService mService;
+
+        BluetoothHidHostBinder(HidHostService svc) {
+            mService = svc;
+        }
+
+        @Override
+        public void cleanup() {
+            mService = null;
+        }
+
+        private HidHostService getService() {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "InputDevice call not allowed for non-active user");
+                return null;
+            }
+
+            if (mService != null && mService.isAvailable()) {
+                return mService;
+            }
+            Log.w(TAG, "Service is null");
+            return null;
+        }
+
+        @Override
+        public boolean connect(BluetoothDevice device) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.connect(device);
+        }
+
+        @Override
+        public boolean disconnect(BluetoothDevice device) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.disconnect(device);
+        }
+
+        @Override
+        public int getConnectionState(BluetoothDevice device) {
+            HidHostService service = getService();
+            if (service == null) {
+                return BluetoothHidHost.STATE_DISCONNECTED;
+            }
+            return service.getConnectionState(device);
+        }
+
+        @Override
+        public List<BluetoothDevice> getConnectedDevices() {
+            return getDevicesMatchingConnectionStates(new int[]{BluetoothProfile.STATE_CONNECTED});
+        }
+
+        @Override
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+            HidHostService service = getService();
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
+            return service.getDevicesMatchingConnectionStates(states);
+        }
+
+        @Override
+        public boolean setPriority(BluetoothDevice device, int priority) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setPriority(device, priority);
+        }
+
+        @Override
+        public int getPriority(BluetoothDevice device) {
+            HidHostService service = getService();
+            if (service == null) {
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
+            return service.getPriority(device);
+        }
+
+        /* The following APIs regarding test app for compliance */
+        @Override
+        public boolean getProtocolMode(BluetoothDevice device) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.getProtocolMode(device);
+        }
+
+        @Override
+        public boolean virtualUnplug(BluetoothDevice device) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.virtualUnplug(device);
+        }
+
+        @Override
+        public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setProtocolMode(device, protocolMode);
+        }
+
+        @Override
+        public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
+                int bufferSize) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.getReport(device, reportType, reportId, bufferSize);
+        }
+
+        @Override
+        public boolean setReport(BluetoothDevice device, byte reportType, String report) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setReport(device, reportType, report);
+        }
+
+        @Override
+        public boolean sendData(BluetoothDevice device, String report) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.sendData(device, report);
+        }
+
+        @Override
+        public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setIdleTime(device, idleTime);
+        }
+
+        @Override
+        public boolean getIdleTime(BluetoothDevice device) {
+            HidHostService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.getIdleTime(device);
+        }
+    }
+
+    ;
+
+    //APIs
+    boolean connect(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "connect: " + device.getAddress());
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (getConnectionState(device) != BluetoothHidHost.STATE_DISCONNECTED) {
+            Log.e(TAG, "Hid Device not disconnected: " + device);
+            return false;
+        }
+        if (getPriority(device) == BluetoothHidHost.PRIORITY_OFF) {
+            Log.e(TAG, "Hid Device PRIORITY_OFF: " + device);
+            return false;
+        }
+
+        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT, device);
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    boolean disconnect(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "disconnect: " + device.getAddress());
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT, device);
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    int getConnectionState(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getConnectionState: " + device.getAddress());
+        if (mInputDevices.get(device) == null) {
+            return BluetoothHidHost.STATE_DISCONNECTED;
+        }
+        return mInputDevices.get(device);
+    }
+
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates()");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
+
+        for (BluetoothDevice device : mInputDevices.keySet()) {
+            int inputDeviceState = getConnectionState(device);
+            for (int state : states) {
+                if (state == inputDeviceState) {
+                    inputDevices.add(device);
+                    break;
+                }
+            }
+        }
+        return inputDevices;
+    }
+
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        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);
+        }
+        return true;
+    }
+
+    public int getPriority(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "getPriority: " + device.getAddress());
+        }
+        int priority = Settings.Global.getInt(getContentResolver(),
+                Settings.Global.getBluetoothHidHostPriorityKey(device.getAddress()),
+                BluetoothProfile.PRIORITY_UNDEFINED);
+        return priority;
+    }
+
+    /* The following APIs regarding test app for compliance */
+    boolean getProtocolMode(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "getProtocolMode: " + device.getAddress());
+        }
+        int state = this.getConnectionState(device);
+        if (state != BluetoothHidHost.STATE_CONNECTED) {
+            return false;
+        }
+        Message msg = mHandler.obtainMessage(MESSAGE_GET_PROTOCOL_MODE, device);
+        mHandler.sendMessage(msg);
+        return true;
+        /* String objectPath = getObjectPathFromAddress(device.getAddress());
+            return getProtocolModeInputDeviceNative(objectPath);*/
+    }
+
+    boolean virtualUnplug(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "virtualUnplug: " + device.getAddress());
+        }
+        int state = this.getConnectionState(device);
+        if (state != BluetoothHidHost.STATE_CONNECTED) {
+            return false;
+        }
+        Message msg = mHandler.obtainMessage(MESSAGE_VIRTUAL_UNPLUG, device);
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "setProtocolMode: " + device.getAddress());
+        }
+        int state = this.getConnectionState(device);
+        if (state != BluetoothHidHost.STATE_CONNECTED) {
+            return false;
+        }
+        Message msg = mHandler.obtainMessage(MESSAGE_SET_PROTOCOL_MODE);
+        msg.obj = device;
+        msg.arg1 = protocolMode;
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "getReport: " + device.getAddress());
+        }
+        int state = this.getConnectionState(device);
+        if (state != BluetoothHidHost.STATE_CONNECTED) {
+            return false;
+        }
+        Message msg = mHandler.obtainMessage(MESSAGE_GET_REPORT);
+        msg.obj = device;
+        Bundle data = new Bundle();
+        data.putByte(BluetoothHidHost.EXTRA_REPORT_TYPE, reportType);
+        data.putByte(BluetoothHidHost.EXTRA_REPORT_ID, reportId);
+        data.putInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE, bufferSize);
+        msg.setData(data);
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    boolean setReport(BluetoothDevice device, byte reportType, String report) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "setReport: " + device.getAddress());
+        }
+        int state = this.getConnectionState(device);
+        if (state != BluetoothHidHost.STATE_CONNECTED) {
+            return false;
+        }
+        Message msg = mHandler.obtainMessage(MESSAGE_SET_REPORT);
+        msg.obj = device;
+        Bundle data = new Bundle();
+        data.putByte(BluetoothHidHost.EXTRA_REPORT_TYPE, reportType);
+        data.putString(BluetoothHidHost.EXTRA_REPORT, report);
+        msg.setData(data);
+        mHandler.sendMessage(msg);
+        return true;
+
+    }
+
+    boolean sendData(BluetoothDevice device, String report) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) {
+            Log.d(TAG, "sendData: " + device.getAddress());
+        }
+        int state = this.getConnectionState(device);
+        if (state != BluetoothHidHost.STATE_CONNECTED) {
+            return false;
+        }
+
+        return sendDataNative(Utils.getByteAddress(device), report);
+    }
+
+    boolean getIdleTime(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) Log.d(TAG, "getIdleTime: " + device.getAddress());
+        int state = this.getConnectionState(device);
+        if (state != BluetoothHidHost.STATE_CONNECTED) {
+            return false;
+        }
+        Message msg = mHandler.obtainMessage(MESSAGE_GET_IDLE_TIME, device);
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    boolean setIdleTime(BluetoothDevice device, byte idleTime) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        if (DBG) Log.d(TAG, "setIdleTime: " + device.getAddress());
+        int state = this.getConnectionState(device);
+        if (state != BluetoothHidHost.STATE_CONNECTED) {
+            return false;
+        }
+        Message msg = mHandler.obtainMessage(MESSAGE_SET_IDLE_TIME);
+        msg.obj = device;
+        Bundle data = new Bundle();
+        data.putByte(BluetoothHidHost.EXTRA_IDLE_TIME, idleTime);
+        msg.setData(data);
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    private void onGetProtocolMode(byte[] address, int mode) {
+        if (DBG) Log.d(TAG, "onGetProtocolMode()");
+        Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_PROTOCOL_MODE);
+        msg.obj = address;
+        msg.arg1 = mode;
+        mHandler.sendMessage(msg);
+    }
+
+    private void onGetIdleTime(byte[] address, int idleTime) {
+        if (DBG) Log.d(TAG, "onGetIdleTime()");
+        Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_IDLE_TIME);
+        msg.obj = address;
+        msg.arg1 = idleTime;
+        mHandler.sendMessage(msg);
+    }
+
+    private void onGetReport(byte[] address, byte[] report, int rptSize) {
+        if (DBG) Log.d(TAG, "onGetReport()");
+        Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_REPORT);
+        msg.obj = address;
+        Bundle data = new Bundle();
+        data.putByteArray(BluetoothHidHost.EXTRA_REPORT, report);
+        data.putInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE, rptSize);
+        msg.setData(data);
+        mHandler.sendMessage(msg);
+    }
+
+    private void onHandshake(byte[] address, int status) {
+        if (DBG) Log.d(TAG, "onHandshake: status=" + status);
+        Message msg = mHandler.obtainMessage(MESSAGE_ON_HANDSHAKE);
+        msg.obj = address;
+        msg.arg1 = status;
+        mHandler.sendMessage(msg);
+    }
+
+    private void onVirtualUnplug(byte[] address, int status) {
+        if (DBG) Log.d(TAG, "onVirtualUnplug: status=" + status);
+        Message msg = mHandler.obtainMessage(MESSAGE_ON_VIRTUAL_UNPLUG);
+        msg.obj = address;
+        msg.arg1 = status;
+        mHandler.sendMessage(msg);
+    }
+
+    private void onConnectStateChanged(byte[] address, int state) {
+        if (DBG) Log.d(TAG, "onConnectStateChanged: state=" + state);
+        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
+        msg.obj = address;
+        msg.arg1 = state;
+        mHandler.sendMessage(msg);
+    }
+
+    // This method does not check for error conditon (newState == prevState)
+    private void broadcastConnectionState(BluetoothDevice device, int newState) {
+        Integer prevStateInteger = mInputDevices.get(device);
+        int prevState = (prevStateInteger == null) ? BluetoothHidHost.STATE_DISCONNECTED
+                : prevStateInteger;
+        if (prevState == newState) {
+            Log.w(TAG, "no state change: " + newState);
+            return;
+        }
+        if (newState == BluetoothProfile.STATE_CONNECTED) {
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HID_HOST);
+        }
+        mInputDevices.put(device, newState);
+
+        /* Notifying the connection state change of the profile before sending the intent for
+           connection state change, as it was causing a race condition, with the UI not being
+           updated with the correct connection state. */
+        Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
+        Intent intent = new Intent(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM);
+    }
+
+    private void broadcastHandshake(BluetoothDevice device, int status) {
+        Intent intent = new Intent(BluetoothHidHost.ACTION_HANDSHAKE);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothHidHost.EXTRA_STATUS, status);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+
+    private void broadcastProtocolMode(BluetoothDevice device, int protocolMode) {
+        Intent intent = new Intent(BluetoothHidHost.ACTION_PROTOCOL_MODE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothHidHost.EXTRA_PROTOCOL_MODE, protocolMode);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        sendBroadcast(intent, BLUETOOTH_PERM);
+        if (DBG) {
+            Log.d(TAG, "Protocol Mode (" + device + "): " + protocolMode);
+        }
+    }
+
+    private void broadcastReport(BluetoothDevice device, byte[] report, int rptSize) {
+        Intent intent = new Intent(BluetoothHidHost.ACTION_REPORT);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothHidHost.EXTRA_REPORT, report);
+        intent.putExtra(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE, rptSize);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+
+    private void broadcastVirtualUnplugStatus(BluetoothDevice device, int status) {
+        Intent intent = new Intent(BluetoothHidHost.ACTION_VIRTUAL_UNPLUG_STATUS);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothHidHost.EXTRA_VIRTUAL_UNPLUG_STATUS, status);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        sendBroadcast(intent, BLUETOOTH_PERM);
+    }
+
+    private void broadcastIdleTime(BluetoothDevice device, int idleTime) {
+        Intent intent = new Intent(BluetoothHidHost.ACTION_IDLE_TIME_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        intent.putExtra(BluetoothHidHost.EXTRA_IDLE_TIME, idleTime);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        sendBroadcast(intent, BLUETOOTH_PERM);
+        if (DBG) {
+            Log.d(TAG, "Idle time (" + device + "): " + idleTime);
+        }
+    }
+
+    /**
+     * 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)
+    public boolean okToConnect(BluetoothDevice device) {
+        AdapterService adapterService = AdapterService.getAdapterService();
+        // Check if adapter service is null.
+        if (adapterService == null) {
+            Log.w(TAG, "okToConnect: adapter service is null");
+            return false;
+        }
+        // Check if this is an incoming connection in Quiet mode.
+        if (adapterService.isQuietModeEnabled() && mTargetDevice == null) {
+            Log.w(TAG, "okToConnect: return false as quiet mode enabled");
+            return false;
+        }
+        // Check priority and accept or reject the connection.
+        int priority = getPriority(device);
+        int bondState = adapterService.getBondState(device);
+        // Allow this connection only if the device is bonded. Any attempt to connect while
+        // bonding would potentially lead to an unauthorized connection.
+        if (bondState != BluetoothDevice.BOND_BONDED) {
+            Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
+            return false;
+        } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED
+                && priority != BluetoothProfile.PRIORITY_ON
+                && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+            // Otherwise, reject the connection if priority is not valid.
+            Log.w(TAG, "okToConnect: return false, priority=" + priority);
+            return false;
+        }
+        return true;
+    }
+
+    private static int convertHalState(int halState) {
+        switch (halState) {
+            case CONN_STATE_CONNECTED:
+                return BluetoothProfile.STATE_CONNECTED;
+            case CONN_STATE_CONNECTING:
+                return BluetoothProfile.STATE_CONNECTING;
+            case CONN_STATE_DISCONNECTED:
+                return BluetoothProfile.STATE_DISCONNECTED;
+            case CONN_STATE_DISCONNECTING:
+                return BluetoothProfile.STATE_DISCONNECTING;
+            default:
+                Log.e(TAG, "bad hid connection state: " + halState);
+                return BluetoothProfile.STATE_DISCONNECTED;
+        }
+    }
+
+    @Override
+    public void dump(StringBuilder sb) {
+        super.dump(sb);
+        println(sb, "mTargetDevice: " + mTargetDevice);
+        println(sb, "mInputDevices:");
+        for (BluetoothDevice device : mInputDevices.keySet()) {
+            println(sb, "  " + device + " : " + mInputDevices.get(device));
+        }
+    }
+
+    // Constants matching Hal header file bt_hh.h
+    // bthh_connection_state_t
+    private static final int CONN_STATE_CONNECTED = 0;
+    private static final int CONN_STATE_CONNECTING = 1;
+    private static final int CONN_STATE_DISCONNECTED = 2;
+    private static final int CONN_STATE_DISCONNECTING = 3;
+
+    private static native void classInitNative();
+
+    private native void initializeNative();
+
+    private native void cleanupNative();
+
+    private native boolean connectHidNative(byte[] btAddress);
+
+    private native boolean disconnectHidNative(byte[] btAddress);
+
+    private native boolean getProtocolModeNative(byte[] btAddress);
+
+    private native boolean virtualUnPlugNative(byte[] btAddress);
+
+    private native boolean setProtocolModeNative(byte[] btAddress, byte protocolMode);
+
+    private native boolean getReportNative(byte[] btAddress, byte reportType, byte reportId,
+            int bufferSize);
+
+    private native boolean setReportNative(byte[] btAddress, byte reportType, String report);
+
+    private native boolean sendDataNative(byte[] btAddress, String report);
+
+    private native boolean setIdleTimeNative(byte[] btAddress, byte idleTime);
+
+    private native boolean getIdleTimeNative(byte[] btAddress);
+}
diff --git a/src/com/android/bluetooth/hid/HidService.java b/src/com/android/bluetooth/hid/HidService.java
deleted file mode 100644
index 9b319dd..0000000
--- a/src/com/android/bluetooth/hid/HidService.java
+++ /dev/null
@@ -1,786 +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.hid;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.IBluetoothInputDevice;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Settings;
-import android.util.Log;
-
-import com.android.bluetooth.btservice.AdapterService;
-import com.android.bluetooth.btservice.ProfileService;
-import com.android.bluetooth.Utils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Provides Bluetooth Hid Host profile, as a service in
- * the Bluetooth application.
- * @hide
- */
-public class HidService extends ProfileService {
-    private static final boolean DBG = true;
-    private static final String TAG = "HidService";
-
-    private Map<BluetoothDevice, Integer> mInputDevices;
-    private boolean mNativeAvailable;
-    private static HidService sHidService;
-    private BluetoothDevice mTargetDevice = null;
-
-    private static final int MESSAGE_CONNECT = 1;
-    private static final int MESSAGE_DISCONNECT = 2;
-    private static final int MESSAGE_CONNECT_STATE_CHANGED = 3;
-    private static final int MESSAGE_GET_PROTOCOL_MODE = 4;
-    private static final int MESSAGE_VIRTUAL_UNPLUG = 5;
-    private static final int MESSAGE_ON_GET_PROTOCOL_MODE = 6;
-    private static final int MESSAGE_SET_PROTOCOL_MODE = 7;
-    private static final int MESSAGE_GET_REPORT = 8;
-    private static final int MESSAGE_ON_GET_REPORT = 9;
-    private static final int MESSAGE_SET_REPORT = 10;
-    private static final int MESSAGE_SEND_DATA = 11;
-    private static final int MESSAGE_ON_VIRTUAL_UNPLUG = 12;
-    private static final int MESSAGE_ON_HANDSHAKE = 13;
-    private static final int MESSAGE_GET_IDLE_TIME = 14;
-    private static final int MESSAGE_ON_GET_IDLE_TIME = 15;
-    private static final int MESSAGE_SET_IDLE_TIME = 16;
-
-    static {
-        classInitNative();
-    }
-
-    public String getName() {
-        return TAG;
-    }
-
-    public IProfileServiceBinder initBinder() {
-        return new BluetoothInputDeviceBinder(this);
-    }
-
-    protected boolean start() {
-        mInputDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>());
-        initializeNative();
-        mNativeAvailable=true;
-        setHidService(this);
-        return true;
-    }
-
-    protected boolean stop() {
-        if (DBG) log("Stopping Bluetooth HidService");
-        return true;
-    }
-
-    protected boolean cleanup() {
-        if (mNativeAvailable) {
-            cleanupNative();
-            mNativeAvailable=false;
-        }
-
-        if(mInputDevices != null) {
-            for (BluetoothDevice device : mInputDevices.keySet()) {
-                int inputDeviceState = getConnectionState(device);
-                if (inputDeviceState != BluetoothProfile.STATE_DISCONNECTED) {
-                    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
-                }
-            }
-            mInputDevices.clear();
-        }
-        clearHidService();
-        return true;
-    }
-
-    public static synchronized HidService getHidService(){
-        if (sHidService != null && sHidService.isAvailable()) {
-            if (DBG) Log.d(TAG, "getHidService(): returning " + sHidService);
-            return sHidService;
-        }
-        if (DBG)  {
-            if (sHidService == null) {
-                Log.d(TAG, "getHidService(): service is NULL");
-            } else if (!(sHidService.isAvailable())) {
-                Log.d(TAG,"getHidService(): service is not available");
-            }
-        }
-        return null;
-    }
-
-    private static synchronized void setHidService(HidService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) Log.d(TAG, "setHidService(): set to: " + sHidService);
-            sHidService = instance;
-        } else {
-            if (DBG)  {
-                if (sHidService == null) {
-                    Log.d(TAG, "setHidService(): service not available");
-                } else if (!sHidService.isAvailable()) {
-                    Log.d(TAG,"setHidService(): service is cleaning up");
-                }
-            }
-        }
-    }
-
-    private static synchronized void clearHidService() {
-        sHidService = null;
-    }
-
-
-    private final Handler mHandler = new Handler() {
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MESSAGE_CONNECT:
-                {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    if (!connectHidNative(Utils.getByteAddress(device)) ) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING);
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
-                        break;
-                    }
-                    mTargetDevice = device;
-                }
-                    break;
-                case MESSAGE_DISCONNECT:
-                {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    if (!disconnectHidNative(Utils.getByteAddress(device)) ) {
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING);
-                        broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
-                        break;
-                    }
-                }
-                    break;
-                case MESSAGE_CONNECT_STATE_CHANGED:
-                {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
-                    int halState = msg.arg1;
-                    Integer prevStateInteger = mInputDevices.get(device);
-                    int prevState = (prevStateInteger == null) ?
-                        BluetoothInputDevice.STATE_DISCONNECTED :prevStateInteger;
-                    if(DBG) Log.d(TAG, "MESSAGE_CONNECT_STATE_CHANGED newState:"+
-                        convertHalState(halState)+", prevState:"+prevState);
-                    if(halState == CONN_STATE_CONNECTED &&
-                       prevState == BluetoothInputDevice.STATE_DISCONNECTED &&
-                       (!okToConnect(device))) {
-                        if (DBG) Log.d(TAG,"Incoming HID connection rejected");
-                        disconnectHidNative(Utils.getByteAddress(device));
-                    } else {
-                        broadcastConnectionState(device, convertHalState(halState));
-                    }
-                    if (halState == CONN_STATE_CONNECTED &&
-                        (mTargetDevice != null && mTargetDevice.equals(device))) {
-                        mTargetDevice = null;
-                        // local device originated connection to hid device, move out
-                        // of quiet mode
-                        AdapterService adapterService = AdapterService.getAdapterService();
-                        adapterService.enable(false);
-                    }
-                }
-                    break;
-                case MESSAGE_GET_PROTOCOL_MODE:
-                {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    if(!getProtocolModeNative(Utils.getByteAddress(device)) ) {
-                        Log.e(TAG, "Error: get protocol mode native returns false");
-                    }
-                }
-                break;
-
-                case MESSAGE_ON_GET_PROTOCOL_MODE:
-                {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
-                    int protocolMode = msg.arg1;
-                    broadcastProtocolMode(device, protocolMode);
-                }
-                break;
-                case MESSAGE_VIRTUAL_UNPLUG:
-                {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    if(!virtualUnPlugNative(Utils.getByteAddress(device))) {
-                        Log.e(TAG, "Error: virtual unplug native returns false");
-                    }
-                }
-                break;
-                case MESSAGE_SET_PROTOCOL_MODE:
-                {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    byte protocolMode = (byte) msg.arg1;
-                    log("sending set protocol mode(" + protocolMode + ")");
-                    if(!setProtocolModeNative(Utils.getByteAddress(device), protocolMode)) {
-                        Log.e(TAG, "Error: set protocol mode native returns false");
-                    }
-                }
-                break;
-                case MESSAGE_GET_REPORT:
-                {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    Bundle data = msg.getData();
-                    byte reportType = data.getByte(BluetoothInputDevice.EXTRA_REPORT_TYPE);
-                    byte reportId = data.getByte(BluetoothInputDevice.EXTRA_REPORT_ID);
-                    int bufferSize = data.getInt(BluetoothInputDevice.EXTRA_REPORT_BUFFER_SIZE);
-                    if(!getReportNative(Utils.getByteAddress(device), reportType, reportId, bufferSize)) {
-                        Log.e(TAG, "Error: get report native returns false");
-                    }
-                }
-                break;
-                case MESSAGE_ON_GET_REPORT:
-                {
-                    BluetoothDevice device = getDevice((byte[])msg.obj);
-                    Bundle data = msg.getData();
-                    byte[] report = data.getByteArray(BluetoothInputDevice.EXTRA_REPORT);
-                    int bufferSize = data.getInt(BluetoothInputDevice.EXTRA_REPORT_BUFFER_SIZE);
-                    broadcastReport(device, report, bufferSize);
-                }
-                break;
-                case MESSAGE_ON_HANDSHAKE:
-                {
-                    BluetoothDevice device = getDevice((byte[])msg.obj);
-                    int status = msg.arg1;
-                    broadcastHandshake(device, status);
-                }
-                break;
-                case MESSAGE_SET_REPORT:
-                {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    Bundle data = msg.getData();
-                    byte reportType = data.getByte(BluetoothInputDevice.EXTRA_REPORT_TYPE);
-                    String report = data.getString(BluetoothInputDevice.EXTRA_REPORT);
-                    if(!setReportNative(Utils.getByteAddress(device), reportType, report)) {
-                        Log.e(TAG, "Error: set report native returns false");
-                    }
-                }
-                break;
-                case MESSAGE_SEND_DATA:
-                {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    Bundle data = msg.getData();
-                    String report = data.getString(BluetoothInputDevice.EXTRA_REPORT);
-                    if(!sendDataNative(Utils.getByteAddress(device), report)) {
-                        Log.e(TAG, "Error: send data native returns false");
-                    }
-                }
-                break;
-                case MESSAGE_ON_VIRTUAL_UNPLUG:
-                {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
-                    int status = msg.arg1;
-                    broadcastVirtualUnplugStatus(device, status);
-                }
-                break;
-                case MESSAGE_GET_IDLE_TIME: {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    if (!getIdleTimeNative(Utils.getByteAddress(device))) {
-                        Log.e(TAG, "Error: get idle time native returns false");
-                    }
-                } break;
-                case MESSAGE_ON_GET_IDLE_TIME: {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
-                    int idleTime = msg.arg1;
-                    broadcastIdleTime(device, idleTime);
-                } break;
-                case MESSAGE_SET_IDLE_TIME: {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    Bundle data = msg.getData();
-                    byte idleTime = data.getByte(BluetoothInputDevice.EXTRA_IDLE_TIME);
-                    if (!setIdleTimeNative(Utils.getByteAddress(device), idleTime)) {
-                        Log.e(TAG, "Error: get idle time native returns false");
-                    }
-                } break;
-            }
-        }
-    };
-
-    /**
-     * Handlers for incoming service calls
-     */
-    private static class BluetoothInputDeviceBinder extends IBluetoothInputDevice.Stub implements IProfileServiceBinder{
-        private HidService mService;
-        public BluetoothInputDeviceBinder(HidService svc) {
-            mService = svc;
-        }
-
-        public boolean cleanup() {
-            mService = null;
-            return true;
-        }
-
-        private HidService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG,"InputDevice call not allowed for non-active user");
-                return null;
-            }
-
-            if (mService  != null && mService.isAvailable()) {
-                return mService;
-            }
-            return null;
-        }
-
-        public boolean connect(BluetoothDevice device) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.connect(device);
-        }
-
-        public boolean disconnect(BluetoothDevice device) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.disconnect(device);
-        }
-
-        public int getConnectionState(BluetoothDevice device) {
-            HidService service = getService();
-            if (service == null) return BluetoothInputDevice.STATE_DISCONNECTED;
-            return service.getConnectionState(device);
-        }
-
-        public List<BluetoothDevice> getConnectedDevices() {
-            return getDevicesMatchingConnectionStates(
-                    new int[] {BluetoothProfile.STATE_CONNECTED});
-        }
-
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            HidService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
-            return service.getDevicesMatchingConnectionStates(states);
-        }
-
-        public boolean setPriority(BluetoothDevice device, int priority) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.setPriority(device, priority);
-        }
-
-        public int getPriority(BluetoothDevice device) {
-            HidService service = getService();
-            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
-            return service.getPriority(device);
-        }
-
-        /* The following APIs regarding test app for compliance */
-        public boolean getProtocolMode(BluetoothDevice device) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.getProtocolMode(device);
-        }
-
-        public boolean virtualUnplug(BluetoothDevice device) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.virtualUnplug(device);
-        }
-
-        public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.setProtocolMode(device, protocolMode);
-        }
-        
-        public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.getReport(device, reportType, reportId, bufferSize) ;
-        }
-
-        public boolean setReport(BluetoothDevice device, byte reportType, String report) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.setReport(device, reportType, report);
-        }
-
-        public boolean sendData(BluetoothDevice device, String report) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.sendData(device, report);
-        }
-
-        public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.setIdleTime(device, idleTime);
-        }
-
-        public boolean getIdleTime(BluetoothDevice device) {
-            HidService service = getService();
-            if (service == null) return false;
-            return service.getIdleTime(device);
-        }
-    };
-
-    //APIs
-    boolean connect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        if (getConnectionState(device) != BluetoothInputDevice.STATE_DISCONNECTED) {
-            Log.e(TAG, "Hid Device not disconnected: " + device);
-            return false;
-        }
-        if (getPriority(device) == BluetoothInputDevice.PRIORITY_OFF) {
-            Log.e(TAG, "Hid Device PRIORITY_OFF: " + device);
-            return false;
-        }
-
-        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT, device);
-        mHandler.sendMessage(msg);
-        return true;
-    }
-
-    boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT,device);
-        mHandler.sendMessage(msg);
-        return true;
-    }
-
-    int getConnectionState(BluetoothDevice device) {
-        if (mInputDevices.get(device) == null) {
-            return BluetoothInputDevice.STATE_DISCONNECTED;
-        }
-        return mInputDevices.get(device);
-    }
-
-    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
-
-        for (BluetoothDevice device: mInputDevices.keySet()) {
-            int inputDeviceState = getConnectionState(device);
-            for (int state : states) {
-                if (state == inputDeviceState) {
-                    inputDevices.add(device);
-                    break;
-                }
-            }
-        }
-        return inputDevices;
-    }
-
-    public boolean setPriority(BluetoothDevice device, int priority) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH_ADMIN permission");
-        Settings.Global.putInt(getContentResolver(),
-            Settings.Global.getBluetoothInputDevicePriorityKey(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.getBluetoothInputDevicePriorityKey(device.getAddress()),
-            BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
-    }
-
-    /* The following APIs regarding test app for compliance */
-    boolean getProtocolMode(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH_ADMIN permission");
-        int state = this.getConnectionState(device);
-        if (state != BluetoothInputDevice.STATE_CONNECTED) {
-            return false;
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_GET_PROTOCOL_MODE,device);
-        mHandler.sendMessage(msg);
-        return true;
-        /* String objectPath = getObjectPathFromAddress(device.getAddress());
-            return getProtocolModeInputDeviceNative(objectPath);*/
-    }
-
-    boolean virtualUnplug(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH_ADMIN permission");
-        int state = this.getConnectionState(device);
-        if (state != BluetoothInputDevice.STATE_CONNECTED) {
-            return false;
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_VIRTUAL_UNPLUG,device);
-        mHandler.sendMessage(msg);
-        return true;
-    }
-
-    boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH_ADMIN permission");
-        int state = this.getConnectionState(device);
-        if (state != BluetoothInputDevice.STATE_CONNECTED) {
-            return false;
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_SET_PROTOCOL_MODE);
-        msg.obj = device;
-        msg.arg1 = protocolMode;
-        mHandler.sendMessage(msg);
-        return true ;
-    }
-
-    boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                       "Need BLUETOOTH_ADMIN permission");
-        int state = this.getConnectionState(device);
-        if (state != BluetoothInputDevice.STATE_CONNECTED) {
-            return false;
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_GET_REPORT);
-        msg.obj = device;
-        Bundle data = new Bundle();
-        data.putByte(BluetoothInputDevice.EXTRA_REPORT_TYPE, reportType);
-        data.putByte(BluetoothInputDevice.EXTRA_REPORT_ID, reportId);
-        data.putInt(BluetoothInputDevice.EXTRA_REPORT_BUFFER_SIZE, bufferSize);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
-        return true ;
-    }
-
-    boolean setReport(BluetoothDevice device, byte reportType, String report) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                                   "Need BLUETOOTH_ADMIN permission");
-        int state = this.getConnectionState(device);
-        if (state != BluetoothInputDevice.STATE_CONNECTED) {
-            return false;
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_SET_REPORT);
-        msg.obj = device;
-        Bundle data = new Bundle();
-        data.putByte(BluetoothInputDevice.EXTRA_REPORT_TYPE, reportType);
-        data.putString(BluetoothInputDevice.EXTRA_REPORT, report);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
-        return true ;
-
-    }
-
-    boolean sendData(BluetoothDevice device, String report) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
-                                                   "Need BLUETOOTH_ADMIN permission");
-        int state = this.getConnectionState(device);
-        if (state != BluetoothInputDevice.STATE_CONNECTED) {
-            return false;
-        }
-
-        return sendDataNative(Utils.getByteAddress(device), report);
-    }
-
-    boolean getIdleTime(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        int state = this.getConnectionState(device);
-        if (state != BluetoothInputDevice.STATE_CONNECTED) {
-            return false;
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_GET_IDLE_TIME, device);
-        mHandler.sendMessage(msg);
-        return true;
-    }
-
-    boolean setIdleTime(BluetoothDevice device, byte idleTime) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        int state = this.getConnectionState(device);
-        if (state != BluetoothInputDevice.STATE_CONNECTED) {
-            return false;
-        }
-        Message msg = mHandler.obtainMessage(MESSAGE_SET_IDLE_TIME);
-        msg.obj = device;
-        Bundle data = new Bundle();
-        data.putByte(BluetoothInputDevice.EXTRA_IDLE_TIME, idleTime);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
-        return true;
-    }
-
-    private void onGetProtocolMode(byte[] address, int mode) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_PROTOCOL_MODE);
-        msg.obj = address;
-        msg.arg1 = mode;
-        mHandler.sendMessage(msg);
-    }
-
-    private void onGetIdleTime(byte[] address, int idleTime) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_IDLE_TIME);
-        msg.obj = address;
-        msg.arg1 = idleTime;
-        mHandler.sendMessage(msg);
-    }
-
-    private void onGetReport(byte[] address, byte[] report, int rpt_size) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_REPORT);
-        msg.obj = address;
-        Bundle data = new Bundle();
-        data.putByteArray(BluetoothInputDevice.EXTRA_REPORT, report);
-        data.putInt(BluetoothInputDevice.EXTRA_REPORT_BUFFER_SIZE, rpt_size);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
-    }
-
-    private void onHandshake(byte[] address, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_HANDSHAKE);
-        msg.obj = address;
-        msg.arg1 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    private void onVirtualUnplug(byte[] address, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_VIRTUAL_UNPLUG);
-        msg.obj = address;
-        msg.arg1 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    private void onConnectStateChanged(byte[] address, int state) {
-        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
-        msg.obj = address;
-        msg.arg1 = state;
-        mHandler.sendMessage(msg);
-    }
-
-    // This method does not check for error conditon (newState == prevState)
-    private void broadcastConnectionState(BluetoothDevice device, int newState) {
-        Integer prevStateInteger = mInputDevices.get(device);
-        int prevState = (prevStateInteger == null) ? BluetoothInputDevice.STATE_DISCONNECTED : 
-                                                     prevStateInteger;
-        if (prevState == newState) {
-            Log.w(TAG, "no state change: " + newState);
-            return;
-        }
-        mInputDevices.put(device, newState);
-
-        /* Notifying the connection state change of the profile before sending the intent for
-           connection state change, as it was causing a race condition, with the UI not being
-           updated with the correct connection state. */
-        log("Connection state " + device + ": " + prevState + "->" + newState);
-        Intent intent = new Intent(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
-        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
-        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
-    }
-
-    private void broadcastHandshake(BluetoothDevice device, int status) {
-        Intent intent = new Intent(BluetoothInputDevice.ACTION_HANDSHAKE);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.putExtra(BluetoothInputDevice.EXTRA_STATUS, status);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
-    }
-
-    private void broadcastProtocolMode(BluetoothDevice device, int protocolMode) {
-        Intent intent = new Intent(BluetoothInputDevice.ACTION_PROTOCOL_MODE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.putExtra(BluetoothInputDevice.EXTRA_PROTOCOL_MODE, protocolMode);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
-        if (DBG) log("Protocol Mode (" + device + "): " + protocolMode);
-    }
-
-    private void broadcastReport(BluetoothDevice device, byte[] report, int rpt_size) {
-        Intent intent = new Intent(BluetoothInputDevice.ACTION_REPORT);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.putExtra(BluetoothInputDevice.EXTRA_REPORT, report);
-        intent.putExtra(BluetoothInputDevice.EXTRA_REPORT_BUFFER_SIZE, rpt_size);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
-    }
-
-    private void broadcastVirtualUnplugStatus(BluetoothDevice device, int status) {
-        Intent intent = new Intent(BluetoothInputDevice.ACTION_VIRTUAL_UNPLUG_STATUS);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.putExtra(BluetoothInputDevice.EXTRA_VIRTUAL_UNPLUG_STATUS, status);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
-    }
-
-    private void broadcastIdleTime(BluetoothDevice device, int idleTime) {
-        Intent intent = new Intent(BluetoothInputDevice.ACTION_IDLE_TIME_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        intent.putExtra(BluetoothInputDevice.EXTRA_IDLE_TIME, idleTime);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
-        if (DBG) log("Idle time (" + device + "): " + idleTime);
-    }
-
-    private boolean okToConnect(BluetoothDevice device) {
-        AdapterService adapterService = AdapterService.getAdapterService();
-        //check if it is inbound connection in Quiet mode, priority and Bond status
-        //to decide if its ok to allow this connection
-        if((adapterService == null)||
-           ((adapterService.isQuietModeEnabled()) &&(mTargetDevice == null)) ||
-           (BluetoothProfile.PRIORITY_OFF == getPriority(device)) ||
-           (device.getBondState() == BluetoothDevice.BOND_NONE))
-            return false;
-
-        if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
-            return false;
-        }
-
-        return true;
-    }
-    private static int convertHalState(int halState) {
-        switch (halState) {
-            case CONN_STATE_CONNECTED:
-                return BluetoothProfile.STATE_CONNECTED;
-            case CONN_STATE_CONNECTING:
-                return BluetoothProfile.STATE_CONNECTING;
-            case CONN_STATE_DISCONNECTED:
-                return BluetoothProfile.STATE_DISCONNECTED;
-            case CONN_STATE_DISCONNECTING:
-                return BluetoothProfile.STATE_DISCONNECTING;
-            default:
-                Log.e(TAG, "bad hid connection state: " + halState);
-                return BluetoothProfile.STATE_DISCONNECTED;
-        }
-    }
-
-    @Override
-    public void dump(StringBuilder sb) {
-        super.dump(sb);
-        println(sb, "mTargetDevice: " + mTargetDevice);
-        println(sb, "mInputDevices:");
-        for (BluetoothDevice device : mInputDevices.keySet()) {
-            println(sb, "  " + device + " : " + mInputDevices.get(device));
-        }
-    }
-
-    // Constants matching Hal header file bt_hh.h
-    // bthh_connection_state_t
-    private final static int CONN_STATE_CONNECTED = 0;
-    private final static int CONN_STATE_CONNECTING = 1;
-    private final static int CONN_STATE_DISCONNECTED = 2;
-    private final static int CONN_STATE_DISCONNECTING = 3;
-
-    private native static void classInitNative();
-    private native void initializeNative();
-    private native void cleanupNative();
-    private native boolean connectHidNative(byte[] btAddress);
-    private native boolean disconnectHidNative(byte[] btAddress);
-    private native boolean getProtocolModeNative(byte[] btAddress);
-    private native boolean virtualUnPlugNative(byte[] btAddress);
-    private native boolean setProtocolModeNative(byte[] btAddress, byte protocolMode);
-    private native boolean getReportNative(byte[]btAddress, byte reportType, byte reportId, int bufferSize);
-    private native boolean setReportNative(byte[] btAddress, byte reportType, String report);
-    private native boolean sendDataNative(byte[] btAddress, String report);
-    private native boolean setIdleTimeNative(byte[] btAddress, byte idleTime);
-    private native boolean getIdleTimeNative(byte[] btAddress);
-}
diff --git a/src/com/android/bluetooth/map/BluetoothMapAccountItem.java b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
index 1a493b8..cb9481b 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountItem.java
@@ -23,7 +23,7 @@
  * It can be used for both Email Apps (group Parent item) and Accounts (Group child Item).
  *
  */
-public class BluetoothMapAccountItem implements Comparable<BluetoothMapAccountItem>{
+public class BluetoothMapAccountItem implements Comparable<BluetoothMapAccountItem> {
     private static final String TAG = "BluetoothMapAccountItem";
 
     private static final boolean D = BluetoothMapService.DEBUG;
@@ -50,25 +50,26 @@
         this.mProviderAuthority = authority;
         this.mType = appType;
         this.mBase_uri_no_account = "content://" + authority;
-        this.mBase_uri = mBase_uri_no_account + "/"+id;
+        this.mBase_uri = mBase_uri_no_account + "/" + id;
         this.mUci = uci;
         this.mUciPrefix = uciPrefix;
     }
 
     public static BluetoothMapAccountItem create(String id, String name, String packageName,
             String authority, Drawable icon, BluetoothMapUtils.TYPE appType) {
-        return new BluetoothMapAccountItem(id, name, packageName, authority,
-                icon, appType, null, null);
+        return new BluetoothMapAccountItem(id, name, packageName, authority, icon, appType, null,
+                null);
     }
 
     public static BluetoothMapAccountItem create(String id, String name, String packageName,
             String authority, Drawable icon, BluetoothMapUtils.TYPE appType, String uci,
             String uciPrefix) {
-        return new BluetoothMapAccountItem(id, name, packageName, authority,
-                icon, appType, uci, uciPrefix);
+        return new BluetoothMapAccountItem(id, name, packageName, authority, icon, appType, uci,
+                uciPrefix);
     }
+
     public long getAccountId() {
-        if(mId != null) {
+        if (mId != null) {
             return Long.parseLong(mId);
         }
         return -1;
@@ -78,46 +79,60 @@
         return mUci;
     }
 
-    public String getUciPrefix(){
+    public String getUciPrefix() {
         return mUciPrefix;
     }
 
-    public String getUciFull(){
-        if(mUci == null)
+    public String getUciFull() {
+        if (mUci == null) {
             return null;
-        if(mUciPrefix == null)
+        }
+        if (mUciPrefix == null) {
             return null;
+        }
         return new StringBuilder(mUciPrefix).append(":").append(mUci).toString();
     }
 
     @Override
     public int compareTo(BluetoothMapAccountItem other) {
 
-        if(!other.mId.equals(this.mId)){
-            if(V) Log.d(TAG, "Wrong id : " + this.mId + " vs " + other.mId);
+        if (!other.mId.equals(this.mId)) {
+            if (V) {
+                Log.d(TAG, "Wrong id : " + this.mId + " vs " + other.mId);
+            }
             return -1;
         }
-        if(!other.mName.equals(this.mName)){
-            if(V) Log.d(TAG, "Wrong name : " + this.mName + " vs " + other.mName);
+        if (!other.mName.equals(this.mName)) {
+            if (V) {
+                Log.d(TAG, "Wrong name : " + this.mName + " vs " + other.mName);
+            }
             return -1;
         }
-        if(!other.mPackageName.equals(this.mPackageName)){
-            if(V) Log.d(TAG, "Wrong packageName : " + this.mPackageName + " vs "
-                    + other.mPackageName);
-             return -1;
-        }
-        if(!other.mProviderAuthority.equals(this.mProviderAuthority)){
-            if(V) Log.d(TAG, "Wrong providerName : " + this.mProviderAuthority + " vs " 
-                    + other.mProviderAuthority);
+        if (!other.mPackageName.equals(this.mPackageName)) {
+            if (V) {
+                Log.d(TAG,
+                        "Wrong packageName : " + this.mPackageName + " vs " + other.mPackageName);
+            }
             return -1;
         }
-        if(other.mIsChecked != this.mIsChecked){
-            if(V) Log.d(TAG, "Wrong isChecked : " + this.mIsChecked + " vs " + other.mIsChecked);
+        if (!other.mProviderAuthority.equals(this.mProviderAuthority)) {
+            if (V) {
+                Log.d(TAG, "Wrong providerName : " + this.mProviderAuthority + " vs "
+                        + other.mProviderAuthority);
+            }
             return -1;
         }
-        if(!other.mType.equals(this.mType)){
-            if(V) Log.d(TAG, "Wrong appType : " + this.mType + " vs " + other.mType);
-             return -1;
+        if (other.mIsChecked != this.mIsChecked) {
+            if (V) {
+                Log.d(TAG, "Wrong isChecked : " + this.mIsChecked + " vs " + other.mIsChecked);
+            }
+            return -1;
+        }
+        if (!other.mType.equals(this.mType)) {
+            if (V) {
+                Log.d(TAG, "Wrong appType : " + this.mType + " vs " + other.mType);
+            }
+            return -1;
         }
         return 0;
     }
@@ -128,47 +143,59 @@
         int result = 1;
         result = prime * result + ((mId == null) ? 0 : mId.hashCode());
         result = prime * result + ((mName == null) ? 0 : mName.hashCode());
-        result = prime * result
-                + ((mPackageName == null) ? 0 : mPackageName.hashCode());
-        result = prime * result
-                + ((mProviderAuthority == null) ? 0 : mProviderAuthority.hashCode());
+        result = prime * result + ((mPackageName == null) ? 0 : mPackageName.hashCode());
+        result =
+                prime * result + ((mProviderAuthority == null) ? 0 : mProviderAuthority.hashCode());
         return result;
     }
 
     @Override
     public boolean equals(Object obj) {
-        if (this == obj)
+        if (this == obj) {
             return true;
-        if (obj == null)
+        }
+        if (obj == null) {
             return false;
-        if (getClass() != obj.getClass())
+        }
+        if (getClass() != obj.getClass()) {
             return false;
+        }
         BluetoothMapAccountItem other = (BluetoothMapAccountItem) obj;
         if (mId == null) {
-            if (other.mId != null)
+            if (other.mId != null) {
                 return false;
-        } else if (!mId.equals(other.mId))
+            }
+        } else if (!mId.equals(other.mId)) {
             return false;
+        }
         if (mName == null) {
-            if (other.mName != null)
+            if (other.mName != null) {
                 return false;
-        } else if (!mName.equals(other.mName))
+            }
+        } else if (!mName.equals(other.mName)) {
             return false;
+        }
         if (mPackageName == null) {
-            if (other.mPackageName != null)
+            if (other.mPackageName != null) {
                 return false;
-        } else if (!mPackageName.equals(other.mPackageName))
+            }
+        } else if (!mPackageName.equals(other.mPackageName)) {
             return false;
+        }
         if (mProviderAuthority == null) {
-            if (other.mProviderAuthority != null)
+            if (other.mProviderAuthority != null) {
                 return false;
-        } else if (!mProviderAuthority.equals(other.mProviderAuthority))
+            }
+        } else if (!mProviderAuthority.equals(other.mProviderAuthority)) {
             return false;
+        }
         if (mType == null) {
-            if (other.mType != null)
+            if (other.mType != null) {
                 return false;
-        } else if (!mType.equals(other.mType))
+            }
+        } else if (!mType.equals(other.mType)) {
             return false;
+        }
         return true;
     }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
index 8c36840..3ad600b 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAccountLoader.java
@@ -15,17 +15,12 @@
 
 package com.android.bluetooth.map;
 
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.net.Uri;
@@ -33,10 +28,14 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 
-import com.android.bluetooth.map.BluetoothMapAccountItem;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
 
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Objects;
+
 public class BluetoothMapAccountLoader {
     private static final String TAG = "BluetoothMapAccountLoader";
     private static final boolean D = BluetoothMapService.DEBUG;
@@ -48,8 +47,7 @@
     private ContentProviderClient mProviderClient = null;
     private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
 
-    public BluetoothMapAccountLoader(Context ctx)
-    {
+    public BluetoothMapAccountLoader(Context ctx) {
         mContext = ctx;
     }
 
@@ -60,18 +58,17 @@
      * @return LinkedHashMap with the packages as keys(BluetoothMapAccountItem) and
      *          values as ArrayLists of BluetoothMapAccountItems.
      */
-    public LinkedHashMap<BluetoothMapAccountItem,
-                         ArrayList<BluetoothMapAccountItem>> parsePackages(boolean includeIcon) {
+    public LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> parsePackages(
+            boolean includeIcon) {
 
         LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> groups =
-                new LinkedHashMap<BluetoothMapAccountItem,
-                                  ArrayList<BluetoothMapAccountItem>>();
+                new LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>>();
         Intent[] searchIntents = new Intent[2];
         //Array <Intent> searchIntents = new Array <Intent>();
         searchIntents[0] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_EMAIL);
         searchIntents[1] = new Intent(BluetoothMapContract.PROVIDER_INTERFACE_IM);
         // reset the counter every time this method is called.
-        mAccountsEnabledCount=0;
+        mAccountsEnabledCount = 0;
         // find all installed packages and filter out those that do not support Bluetooth Map.
         // this is done by looking for a apps with content providers containing the intent-filter
         // in the manifest file.
@@ -79,31 +76,33 @@
 
         for (Intent searchIntent : searchIntents) {
             List<ResolveInfo> resInfos =
-                mPackageManager.queryIntentContentProviders(searchIntent, 0);
-            if (resInfos != null ) {
-                if(D) Log.d(TAG,"Found " + resInfos.size() + " application(s) with intent "
-                        + searchIntent.getAction().toString());
-                BluetoothMapUtils.TYPE msgType = (searchIntent.getAction().toString() ==
-                        BluetoothMapContract.PROVIDER_INTERFACE_EMAIL) ?
-                        BluetoothMapUtils.TYPE.EMAIL : BluetoothMapUtils.TYPE.IM;
+                    mPackageManager.queryIntentContentProviders(searchIntent, 0);
+            if (resInfos != null) {
+                if (D) {
+                    Log.d(TAG, "Found " + resInfos.size() + " application(s) with intent "
+                            + searchIntent.getAction());
+                }
+                BluetoothMapUtils.TYPE msgType = (Objects.equals(searchIntent.getAction(),
+                        BluetoothMapContract.PROVIDER_INTERFACE_EMAIL))
+                        ? BluetoothMapUtils.TYPE.EMAIL : BluetoothMapUtils.TYPE.IM;
                 for (ResolveInfo rInfo : resInfos) {
-                    if(D) Log.d(TAG,"ResolveInfo " + rInfo.toString());
+                    if (D) {
+                        Log.d(TAG, "ResolveInfo " + rInfo.toString());
+                    }
                     // We cannot rely on apps that have been force-stopped in the
                     // application settings menu.
-                    if ((rInfo.providerInfo.applicationInfo.flags &
-                            ApplicationInfo.FLAG_STOPPED) == 0) {
+                    if ((rInfo.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED)
+                            == 0) {
                         BluetoothMapAccountItem app = createAppItem(rInfo, includeIcon, msgType);
-                        if (app != null){
+                        if (app != null) {
                             ArrayList<BluetoothMapAccountItem> accounts = parseAccounts(app);
                             // we do not want to list apps without accounts
-                            if(accounts.size() > 0)
-                            {// we need to make sure that the "select all" checkbox
-                             // is checked if all accounts in the list are checked
+                            if (accounts.size() > 0) {
+                                // we need to make sure that the "select all" checkbox
+                                // is checked if all accounts in the list are checked
                                 app.mIsChecked = true;
-                                for (BluetoothMapAccountItem acc: accounts)
-                                {
-                                    if(!acc.mIsChecked)
-                                    {
+                                for (BluetoothMapAccountItem acc : accounts) {
+                                    if (!acc.mIsChecked) {
                                         app.mIsChecked = false;
                                         break;
                                     }
@@ -112,13 +111,16 @@
                             }
                         }
                     } else {
-                        if(D)Log.d(TAG,"Ignoring force-stopped authority "
-                                + rInfo.providerInfo.authority +"\n");
+                        if (D) {
+                            Log.d(TAG, "Ignoring force-stopped authority "
+                                    + rInfo.providerInfo.authority + "\n");
+                        }
                     }
                 }
-            }
-            else {
-                if(D) Log.d(TAG,"Found no applications");
+            } else {
+                if (D) {
+                    Log.d(TAG, "Found no applications");
+                }
             }
         }
         return groups;
@@ -127,17 +129,17 @@
     public BluetoothMapAccountItem createAppItem(ResolveInfo rInfo, boolean includeIcon,
             BluetoothMapUtils.TYPE type) {
         String provider = rInfo.providerInfo.authority;
-        if(provider != null) {
+        if (provider != null) {
             String name = rInfo.loadLabel(mPackageManager).toString();
-            if(D)Log.d(TAG,rInfo.providerInfo.packageName + " - " + name +
-                            " - meta-data(provider = " + provider+")\n");
-            BluetoothMapAccountItem app = BluetoothMapAccountItem.create(
-                    "0",
-                    name,
-                    rInfo.providerInfo.packageName,
-                    provider,
-                    (includeIcon == false)? null : rInfo.loadIcon(mPackageManager),
-                    type);
+            if (D) {
+                Log.d(TAG,
+                        rInfo.providerInfo.packageName + " - " + name + " - meta-data(provider = "
+                                + provider + ")\n");
+            }
+            BluetoothMapAccountItem app =
+                    BluetoothMapAccountItem.create("0", name, rInfo.providerInfo.packageName,
+                            provider, (!includeIcon) ? null : rInfo.loadIcon(mPackageManager),
+                            type);
             return app;
         }
 
@@ -149,13 +151,15 @@
      * @param app The parent app object
      * @return An ArrayList of BluetoothMapAccountItems containing all the accounts from the app
      */
-    public ArrayList<BluetoothMapAccountItem> parseAccounts(BluetoothMapAccountItem app)  {
+    public ArrayList<BluetoothMapAccountItem> parseAccounts(BluetoothMapAccountItem app) {
         Cursor c = null;
-        if(D) Log.d(TAG,"Finding accounts for app "+app.getPackageName());
+        if (D) {
+            Log.d(TAG, "Finding accounts for app " + app.getPackageName());
+        }
         ArrayList<BluetoothMapAccountItem> children = new ArrayList<BluetoothMapAccountItem>();
         // Get the list of accounts from the email apps content resolver (if possible)
         mResolver = mContext.getContentResolver();
-        try{
+        try {
             mProviderClient = mResolver.acquireUnstableContentProviderClient(
                     Uri.parse(app.mBase_uri_no_account));
             if (mProviderClient == null) {
@@ -163,71 +167,75 @@
             }
             mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
 
-            Uri uri = Uri.parse(app.mBase_uri_no_account + "/"
-                                + BluetoothMapContract.TABLE_ACCOUNT);
+            Uri uri =
+                    Uri.parse(app.mBase_uri_no_account + "/" + BluetoothMapContract.TABLE_ACCOUNT);
 
-            if(app.getType() == TYPE.IM) {
-                c = mProviderClient.query(uri, BluetoothMapContract.BT_IM_ACCOUNT_PROJECTION,
-                        null, null, BluetoothMapContract.AccountColumns._ID+" DESC");
+            if (app.getType() == TYPE.IM) {
+                c = mProviderClient.query(uri, BluetoothMapContract.BT_IM_ACCOUNT_PROJECTION, null,
+                        null, BluetoothMapContract.AccountColumns._ID + " DESC");
             } else {
-                c = mProviderClient.query(uri, BluetoothMapContract.BT_ACCOUNT_PROJECTION,
-                        null, null, BluetoothMapContract.AccountColumns._ID+" DESC");
+                c = mProviderClient.query(uri, BluetoothMapContract.BT_ACCOUNT_PROJECTION, null,
+                        null, BluetoothMapContract.AccountColumns._ID + " DESC");
             }
-        } catch (RemoteException e){
-            if(D)Log.d(TAG,"Could not establish ContentProviderClient for "+app.getPackageName()+
-                    " - returning empty account list" );
+        } catch (RemoteException e) {
+            if (D) {
+                Log.d(TAG, "Could not establish ContentProviderClient for " + app.getPackageName()
+                        + " - returning empty account list");
+            }
             return children;
         } finally {
-            if (mProviderClient != null)
+            if (mProviderClient != null) {
                 mProviderClient.release();
+            }
         }
 
         if (c != null) {
             c.moveToPosition(-1);
             int idIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns._ID);
-            int dispNameIndex = c.getColumnIndex(
-                    BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME);
+            int dispNameIndex =
+                    c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_DISPLAY_NAME);
             int exposeIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE);
             int uciIndex = c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_UCI);
-            int uciPreIndex = c.getColumnIndex(
-                    BluetoothMapContract.AccountColumns.ACCOUNT_UCI_PREFIX);
+            int uciPreIndex =
+                    c.getColumnIndex(BluetoothMapContract.AccountColumns.ACCOUNT_UCI_PREFIX);
             while (c.moveToNext()) {
-                if(D)Log.d(TAG,"Adding account " + c.getString(dispNameIndex) +
-                        " with ID " + String.valueOf(c.getInt(idIndex)));
+                if (D) {
+                    Log.d(TAG, "Adding account " + c.getString(dispNameIndex) + " with ID " + String
+                            .valueOf(c.getInt(idIndex)));
+                }
                 String uci = null;
                 String uciPrefix = null;
-                if(app.getType() == TYPE.IM){
+                if (app.getType() == TYPE.IM) {
                     uci = c.getString(uciIndex);
                     uciPrefix = c.getString(uciPreIndex);
-                    if(D)Log.d(TAG,"   Account UCI " + uci);
+                    if (D) {
+                        Log.d(TAG, "   Account UCI " + uci);
+                    }
                 }
 
-                BluetoothMapAccountItem child = BluetoothMapAccountItem.create(
-                        String.valueOf((c.getInt(idIndex))),
-                        c.getString(dispNameIndex),
-                        app.getPackageName(),
-                        app.getProviderAuthority(),
-                        null,
-                        app.getType(),
-                        uci,
-                        uciPrefix);
+                BluetoothMapAccountItem child =
+                        BluetoothMapAccountItem.create(String.valueOf((c.getInt(idIndex))),
+                                c.getString(dispNameIndex), app.getPackageName(),
+                                app.getProviderAuthority(), null, app.getType(), uci, uciPrefix);
 
                 child.mIsChecked = (c.getInt(exposeIndex) != 0);
                 child.mIsChecked = true; // TODO: Revert when this works
                 /* update the account counter
                  * so we can make sure that not to many accounts are checked. */
-                if(child.mIsChecked)
-                {
+                if (child.mIsChecked) {
                     mAccountsEnabledCount++;
                 }
                 children.add(child);
             }
             c.close();
         } else {
-            if(D)Log.d(TAG, "query failed");
+            if (D) {
+                Log.d(TAG, "query failed");
+            }
         }
         return children;
     }
+
     /**
      * Gets the number of enabled accounts in total across all supported apps.
      * NOTE that this method should not be called before the parsePackages method
@@ -235,7 +243,9 @@
      * @return number of enabled accounts
      */
     public int getAccountsEnabledCount() {
-        if(D)Log.d(TAG,"Enabled Accounts count:"+ mAccountsEnabledCount);
+        if (D) {
+            Log.d(TAG, "Enabled Accounts count:" + mAccountsEnabledCount);
+        }
         return mAccountsEnabledCount;
     }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
index 2d603f7..b4dfe5a 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppObserver.java
@@ -14,10 +14,6 @@
 */
 package com.android.bluetooth.map;
 
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -31,13 +27,18 @@
 
 import com.android.bluetooth.mapapi.BluetoothMapContract;
 
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Objects;
+
 /**
  * Class to construct content observers for for email applications on the system.
  *
  *
  */
 
-public class BluetoothMapAppObserver{
+public class BluetoothMapAppObserver {
 
     private static final String TAG = "BluetoothMapAppObserver";
 
@@ -45,8 +46,8 @@
     private static final boolean V = BluetoothMapService.VERBOSE;
     /*  */
     private LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mFullList;
-    private LinkedHashMap<String,ContentObserver> mObserverMap =
-            new LinkedHashMap<String,ContentObserver>();
+    private LinkedHashMap<String, ContentObserver> mObserverMap =
+            new LinkedHashMap<String, ContentObserver>();
     private ContentResolver mResolver;
     private Context mContext;
     private BroadcastReceiver mReceiver;
@@ -56,74 +57,92 @@
     private boolean mRegisteredReceiver = false;
 
     public BluetoothMapAppObserver(final Context context, BluetoothMapService mapService) {
-        mContext    = context;
+        mContext = context;
         mMapService = mapService;
-        mResolver   = context.getContentResolver();
-        mLoader     = new BluetoothMapAccountLoader(mContext);
-        mFullList   = mLoader.parsePackages(false); /* Get the current list of apps */
+        mResolver = context.getContentResolver();
+        mLoader = new BluetoothMapAccountLoader(mContext);
+        mFullList = mLoader.parsePackages(false); /* Get the current list of apps */
         createReceiver();
         initObservers();
     }
 
 
     private BluetoothMapAccountItem getApp(String authoritiesName) {
-        if(V) Log.d(TAG, "getApp(): Looking for " + authoritiesName);
-        for(BluetoothMapAccountItem app:mFullList.keySet()){
-            if(V) Log.d(TAG, "  Comparing: " + app.getProviderAuthority());
-            if(app.getProviderAuthority().equals(authoritiesName)) {
-                if(V) Log.d(TAG, "  found " + app.mBase_uri_no_account);
+        if (V) {
+            Log.d(TAG, "getApp(): Looking for " + authoritiesName);
+        }
+        for (BluetoothMapAccountItem app : mFullList.keySet()) {
+            if (V) {
+                Log.d(TAG, "  Comparing: " + app.getProviderAuthority());
+            }
+            if (app.getProviderAuthority().equals(authoritiesName)) {
+                if (V) {
+                    Log.d(TAG, "  found " + app.mBase_uri_no_account);
+                }
                 return app;
             }
         }
-        if(V) Log.d(TAG, "  NOT FOUND!");
+        if (V) {
+            Log.d(TAG, "  NOT FOUND!");
+        }
         return null;
     }
 
     private void handleAccountChanges(String packageNameWithProvider) {
 
-        if(D)Log.d(TAG,"handleAccountChanges (packageNameWithProvider: "
-                        +packageNameWithProvider+"\n");
+        if (D) {
+            Log.d(TAG, "handleAccountChanges (packageNameWithProvider: " + packageNameWithProvider
+                    + "\n");
+        }
         //String packageName = packageNameWithProvider.replaceFirst("\\.[^\\.]+$", "");
         BluetoothMapAccountItem app = getApp(packageNameWithProvider);
-        if(app != null) {
+        if (app != null) {
             ArrayList<BluetoothMapAccountItem> newAccountList = mLoader.parseAccounts(app);
             ArrayList<BluetoothMapAccountItem> oldAccountList = mFullList.get(app);
             ArrayList<BluetoothMapAccountItem> addedAccountList =
-                    (ArrayList<BluetoothMapAccountItem>)newAccountList.clone();
+                    (ArrayList<BluetoothMapAccountItem>) newAccountList.clone();
             // Same as oldAccountList.clone
             ArrayList<BluetoothMapAccountItem> removedAccountList = mFullList.get(app);
-            if (oldAccountList == null)
-                oldAccountList = new ArrayList <BluetoothMapAccountItem>();
-            if (removedAccountList == null)
-                removedAccountList = new ArrayList <BluetoothMapAccountItem>();
+            if (oldAccountList == null) {
+                oldAccountList = new ArrayList<BluetoothMapAccountItem>();
+            }
+            if (removedAccountList == null) {
+                removedAccountList = new ArrayList<BluetoothMapAccountItem>();
+            }
 
             mFullList.put(app, newAccountList);
-            for(BluetoothMapAccountItem newAcc: newAccountList){
-                for(BluetoothMapAccountItem oldAcc: oldAccountList){
-                    if(newAcc.getId() == oldAcc.getId()){
+            for (BluetoothMapAccountItem newAcc : newAccountList) {
+                for (BluetoothMapAccountItem oldAcc : oldAccountList) {
+                    if (Objects.equals(newAcc.getId(), oldAcc.getId())) {
                         // For each match remove from both removed and added lists
                         removedAccountList.remove(oldAcc);
                         addedAccountList.remove(newAcc);
-                        if(!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked){
+                        if (!newAcc.getName().equals(oldAcc.getName()) && newAcc.mIsChecked) {
                             // Name Changed and the acc is visible - Change Name in SDP record
                             mMapService.updateMasInstances(
                                     BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED);
-                            if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED");
+                            if (V) {
+                                Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED");
+                            }
                         }
-                        if(newAcc.mIsChecked != oldAcc.mIsChecked) {
+                        if (newAcc.mIsChecked != oldAcc.mIsChecked) {
                             // Visibility changed
-                            if(newAcc.mIsChecked){
+                            if (newAcc.mIsChecked) {
                                 // account added - create SDP record
                                 mMapService.updateMasInstances(
                                         BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
-                                if(V)Log.d(TAG, "UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " +
-                                        "isChecked changed");
+                                if (V) {
+                                    Log.d(TAG, "UPDATE_MAS_INSTANCES_ACCOUNT_ADDED "
+                                            + "isChecked changed");
+                                }
                             } else {
                                 // account removed - remove SDP record
                                 mMapService.updateMasInstances(
                                         BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
-                                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " +
-                                        "isChecked changed");
+                                if (V) {
+                                    Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED "
+                                            + "isChecked changed");
+                                }
                             }
                         }
                         break;
@@ -131,16 +150,20 @@
                 }
             }
             // Notify on any removed accounts
-            for(BluetoothMapAccountItem removedAcc: removedAccountList){
+            for (BluetoothMapAccountItem removedAcc : removedAccountList) {
                 mMapService.updateMasInstances(
                         BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED);
-                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc);
+                if (V) {
+                    Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED " + removedAcc);
+                }
             }
             // Notify on any new accounts
-            for(BluetoothMapAccountItem addedAcc: addedAccountList){
+            for (BluetoothMapAccountItem addedAcc : addedAccountList) {
                 mMapService.updateMasInstances(
                         BluetoothMapService.UPDATE_MAS_INSTANCES_ACCOUNT_ADDED);
-                if(V)Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc);
+                if (V) {
+                    Log.d(TAG, "    UPDATE_MAS_INSTANCES_ACCOUNT_ADDED " + addedAcc);
+                }
             }
 
         } else {
@@ -152,12 +175,14 @@
     /**
      * Adds a new content observer to the list of content observers.
      * The key for the observer is the uri as string
-     * @param uri uri for the package that supports MAP email
+     * @param app app item for the package that supports MAP email
      */
 
     public void registerObserver(BluetoothMapAccountItem app) {
         Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
-        if (V) Log.d(TAG, "registerObserver for URI "+uri.toString()+"\n");
+        if (V) {
+            Log.d(TAG, "registerObserver for URI " + uri.toString() + "\n");
+        }
         ContentObserver observer = new ContentObserver(null) {
             @Override
             public void onChange(boolean selfChange) {
@@ -166,9 +191,12 @@
 
             @Override
             public void onChange(boolean selfChange, Uri uri) {
-                if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
-                        + " Uri: " + uri + " selfchange: " + selfChange);
-                if(uri != null) {
+                if (V) {
+                    Log.d(TAG,
+                            "onChange on thread: " + Thread.currentThread().getId() + " Uri: " + uri
+                                    + " selfchange: " + selfChange);
+                }
+                if (uri != null) {
                     handleAccountChanges(uri.getHost());
                 } else {
                     Log.e(TAG, "Unable to handle change as the URI is NULL!");
@@ -183,27 +211,35 @@
 
     public void unregisterObserver(BluetoothMapAccountItem app) {
         Uri uri = BluetoothMapContract.buildAccountUri(app.getProviderAuthority());
-        if (V) Log.d(TAG, "unregisterObserver("+uri.toString()+")\n");
+        if (V) {
+            Log.d(TAG, "unregisterObserver(" + uri.toString() + ")\n");
+        }
         mResolver.unregisterContentObserver(mObserverMap.get(uri.toString()));
         mObserverMap.remove(uri.toString());
     }
 
-    private void initObservers(){
-        if(D)Log.d(TAG,"initObservers()");
-        for(BluetoothMapAccountItem app: mFullList.keySet()){
+    private void initObservers() {
+        if (D) {
+            Log.d(TAG, "initObservers()");
+        }
+        for (BluetoothMapAccountItem app : mFullList.keySet()) {
             registerObserver(app);
         }
     }
 
-    private void deinitObservers(){
-        if(D)Log.d(TAG,"deinitObservers()");
-        for(BluetoothMapAccountItem app: mFullList.keySet()){
+    private void deinitObservers() {
+        if (D) {
+            Log.d(TAG, "deinitObservers()");
+        }
+        for (BluetoothMapAccountItem app : mFullList.keySet()) {
             unregisterObserver(app);
         }
     }
 
-    private void createReceiver(){
-        if(D)Log.d(TAG,"createReceiver()\n");
+    private void createReceiver() {
+        if (D) {
+            Log.d(TAG, "createReceiver()\n");
+        }
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -211,13 +247,17 @@
         mReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                if(D)Log.d(TAG,"onReceive\n");
+                if (D) {
+                    Log.d(TAG, "onReceive\n");
+                }
                 String action = intent.getAction();
 
                 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                     Uri data = intent.getData();
                     String packageName = data.getEncodedSchemeSpecificPart();
-                    if(D)Log.d(TAG,"The installed package is: "+ packageName);
+                    if (D) {
+                        Log.d(TAG, "The installed package is: " + packageName);
+                    }
 
                     BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.TYPE.NONE;
                     ResolveInfo resolveInfo = null;
@@ -232,20 +272,22 @@
                     for (Intent searchIntent : searchIntents) {
                         List<ResolveInfo> resInfos =
                                 mPackageManager.queryIntentContentProviders(searchIntent, 0);
-                        if (resInfos != null ) {
-                            if(D) Log.d(TAG,"Found " + resInfos.size()
-                                    + " application(s) with intent "
-                                    + searchIntent.getAction().toString());
+                        if (resInfos != null) {
+                            if (D) {
+                                Log.d(TAG,
+                                        "Found " + resInfos.size() + " application(s) with intent "
+                                                + searchIntent.getAction());
+                            }
                             for (ResolveInfo rInfo : resInfos) {
-                                if(rInfo != null) {
+                                if (rInfo != null) {
                                     // Find out if package contain Bluetooth MAP support
                                     if (packageName.equals(rInfo.providerInfo.packageName)) {
                                         resolveInfo = rInfo;
-                                        if(searchIntent.getAction() ==
-                                                BluetoothMapContract.PROVIDER_INTERFACE_EMAIL){
+                                        if (Objects.equals(searchIntent.getAction(),
+                                                BluetoothMapContract.PROVIDER_INTERFACE_EMAIL)) {
                                             msgType = BluetoothMapUtils.TYPE.EMAIL;
-                                        } else if (searchIntent.getAction() ==
-                                                BluetoothMapContract.PROVIDER_INTERFACE_IM){
+                                        } else if (Objects.equals(searchIntent.getAction(),
+                                                BluetoothMapContract.PROVIDER_INTERFACE_IM)) {
                                             msgType = BluetoothMapUtils.TYPE.IM;
                                         }
                                         break;
@@ -255,12 +297,14 @@
                         }
                     }
                     // if application found with Bluetooth MAP support add to list
-                    if(resolveInfo != null) {
-                        if(D) Log.d(TAG,"Found " + resolveInfo.providerInfo.packageName
-                                + " application of type " + msgType);
-                        BluetoothMapAccountItem app = mLoader.createAppItem(resolveInfo,
-                                false, msgType);
-                        if(app != null) {
+                    if (resolveInfo != null) {
+                        if (D) {
+                            Log.d(TAG, "Found " + resolveInfo.providerInfo.packageName
+                                    + " application of type " + msgType);
+                        }
+                        BluetoothMapAccountItem app =
+                                mLoader.createAppItem(resolveInfo, false, msgType);
+                        if (app != null) {
                             registerObserver(app);
                             // Add all accounts to mFullList
                             ArrayList<BluetoothMapAccountItem> newAccountList =
@@ -269,14 +313,15 @@
                         }
                     }
 
-                }
-                else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
                     Uri data = intent.getData();
                     String packageName = data.getEncodedSchemeSpecificPart();
-                    if(D)Log.d(TAG,"The removed package is: "+ packageName);
+                    if (D) {
+                        Log.d(TAG, "The removed package is: " + packageName);
+                    }
                     BluetoothMapAccountItem app = getApp(packageName);
                     /* Find the object and remove from fullList */
-                    if(app != null) {
+                    if (app != null) {
                         unregisterObserver(app);
                         mFullList.remove(app);
                     }
@@ -285,22 +330,24 @@
         };
         if (!mRegisteredReceiver) {
             try {
-                mContext.registerReceiver(mReceiver,intentFilter);
+                mContext.registerReceiver(mReceiver, intentFilter);
                 mRegisteredReceiver = true;
             } catch (Exception e) {
-                Log.e(TAG,"Unable to register MapAppObserver receiver", e);
+                Log.e(TAG, "Unable to register MapAppObserver receiver", e);
             }
         }
     }
 
-    private void removeReceiver(){
-        if(D)Log.d(TAG,"removeReceiver()\n");
+    private void removeReceiver() {
+        if (D) {
+            Log.d(TAG, "removeReceiver()\n");
+        }
         if (mRegisteredReceiver) {
             try {
                 mRegisteredReceiver = false;
                 mContext.unregisterReceiver(mReceiver);
             } catch (Exception e) {
-                Log.e(TAG,"Unable to unregister mapAppObserver receiver", e);
+                Log.e(TAG, "Unable to unregister mapAppObserver receiver", e);
             }
         }
     }
@@ -310,23 +357,25 @@
      * through MAP.
      * @return Arraylist<BluetoothMapAccountItem> containing all enabled accounts
      */
-    public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems(){
-        if(D)Log.d(TAG,"getEnabledAccountItems()\n");
+    public ArrayList<BluetoothMapAccountItem> getEnabledAccountItems() {
+        if (D) {
+            Log.d(TAG, "getEnabledAccountItems()\n");
+        }
         ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
-        for (BluetoothMapAccountItem app:mFullList.keySet()){
+        for (BluetoothMapAccountItem app : mFullList.keySet()) {
             if (app != null) {
                 ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
                 if (accountList != null) {
-                    for (BluetoothMapAccountItem acc: accountList) {
+                    for (BluetoothMapAccountItem acc : accountList) {
                         if (acc.mIsChecked) {
                             list.add(acc);
                         }
                     }
                 } else {
-                    Log.w(TAG,"getEnabledAccountItems() - No AccountList enabled\n");
+                    Log.w(TAG, "getEnabledAccountItems() - No AccountList enabled\n");
                 }
             } else {
-                Log.w(TAG,"getEnabledAccountItems() - No Account in App enabled\n");
+                Log.w(TAG, "getEnabledAccountItems() - No Account in App enabled\n");
             }
         }
         return list;
@@ -336,10 +385,12 @@
      * Method to get a list of the accounts (across all apps).
      * @return Arraylist<BluetoothMapAccountItem> containing all accounts
      */
-    public ArrayList<BluetoothMapAccountItem> getAllAccountItems(){
-        if(D)Log.d(TAG,"getAllAccountItems()\n");
+    public ArrayList<BluetoothMapAccountItem> getAllAccountItems() {
+        if (D) {
+            Log.d(TAG, "getAllAccountItems()\n");
+        }
         ArrayList<BluetoothMapAccountItem> list = new ArrayList<BluetoothMapAccountItem>();
-        for(BluetoothMapAccountItem app:mFullList.keySet()){
+        for (BluetoothMapAccountItem app : mFullList.keySet()) {
             ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app);
             list.addAll(accountList);
         }
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
index 300b122..c1c2846 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppParams.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
@@ -14,6 +14,10 @@
 */
 package com.android.bluetooth.map;
 
+import android.util.Log;
+
+import com.android.bluetooth.SignedLongLong;
+
 import java.io.UnsupportedEncodingException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -22,10 +26,6 @@
 import java.util.Arrays;
 import java.util.Date;
 
-import android.util.Log;
-
-import com.android.bluetooth.SignedLongLong;
-
 /**
  * This class encapsulates the appParams needed for MAP.
  */
@@ -33,149 +33,149 @@
 
     private static final String TAG = "BluetoothMapAppParams";
 
-    private static final int MAX_LIST_COUNT           = 0x01;
-    private static final int START_OFFSET             = 0x02;
-    private static final int FILTER_MESSAGE_TYPE      = 0x03;
-    private static final int FILTER_PERIOD_BEGIN      = 0x04;
-    private static final int FILTER_PERIOD_END        = 0x05;
-    private static final int FILTER_READ_STATUS       = 0x06;
-    private static final int FILTER_RECIPIENT         = 0x07;
-    private static final int FILTER_ORIGINATOR        = 0x08;
-    private static final int FILTER_PRIORITY          = 0x09;
-    private static final int ATTACHMENT               = 0x0A;
-    private static final int TRANSPARENT              = 0x0B;
-    private static final int RETRY                    = 0x0C;
-    private static final int NEW_MESSAGE              = 0x0D;
-    private static final int NOTIFICATION_STATUS      = 0x0E;
-    private static final int MAS_INSTANCE_ID          = 0x0F;
-    private static final int PARAMETER_MASK           = 0x10;
-    private static final int FOLDER_LISTING_SIZE      = 0x11;
-    private static final int MESSAGE_LISTING_SIZE     = 0x12;
-    private static final int SUBJECT_LENGTH           = 0x13;
-    private static final int CHARSET                  = 0x14;
-    private static final int FRACTION_REQUEST         = 0x15;
-    private static final int FRACTION_DELIVER         = 0x16;
-    private static final int STATUS_INDICATOR         = 0x17;
-    private static final int STATUS_VALUE             = 0x18;
-    private static final int MSE_TIME                 = 0x19;
-    private static final int DATABASE_INDETIFIER      = 0x1A;
-    private static final int CONVO_LIST_VER_COUNTER   = 0x1B;
-    private static final int PRESENCE_AVAILABLE       = 0x1C;
-    private static final int PRESENCE_TEXT            = 0x1D;
-    private static final int LAST_ACTIVITY            = 0x1E;
-    private static final int CHAT_STATE               = 0x1F;
-    private static final int FILTER_CONVO_ID          = 0x20;
-    private static final int CONVO_LISTING_SIZE       = 0x21;
-    private static final int FILTER_PRESENCE          = 0x22;
-    private static final int FILTER_UID_PRESENT       = 0x23;
-    private static final int CHAT_STATE_CONVO_ID      = 0x24;
-    private static final int FOLDER_VER_COUNTER       = 0x25;
-    private static final int FILTER_MESSAGE_HANDLE    = 0x26;
-    private static final int NOTIFICATION_FILTER      = 0x27;
-    private static final int CONVO_PARAMETER_MASK     = 0x28;
+    private static final int MAX_LIST_COUNT = 0x01;
+    private static final int START_OFFSET = 0x02;
+    private static final int FILTER_MESSAGE_TYPE = 0x03;
+    private static final int FILTER_PERIOD_BEGIN = 0x04;
+    private static final int FILTER_PERIOD_END = 0x05;
+    private static final int FILTER_READ_STATUS = 0x06;
+    private static final int FILTER_RECIPIENT = 0x07;
+    private static final int FILTER_ORIGINATOR = 0x08;
+    private static final int FILTER_PRIORITY = 0x09;
+    private static final int ATTACHMENT = 0x0A;
+    private static final int TRANSPARENT = 0x0B;
+    private static final int RETRY = 0x0C;
+    private static final int NEW_MESSAGE = 0x0D;
+    private static final int NOTIFICATION_STATUS = 0x0E;
+    private static final int MAS_INSTANCE_ID = 0x0F;
+    private static final int PARAMETER_MASK = 0x10;
+    private static final int FOLDER_LISTING_SIZE = 0x11;
+    private static final int MESSAGE_LISTING_SIZE = 0x12;
+    private static final int SUBJECT_LENGTH = 0x13;
+    private static final int CHARSET = 0x14;
+    private static final int FRACTION_REQUEST = 0x15;
+    private static final int FRACTION_DELIVER = 0x16;
+    private static final int STATUS_INDICATOR = 0x17;
+    private static final int STATUS_VALUE = 0x18;
+    private static final int MSE_TIME = 0x19;
+    private static final int DATABASE_INDETIFIER = 0x1A;
+    private static final int CONVO_LIST_VER_COUNTER = 0x1B;
+    private static final int PRESENCE_AVAILABLE = 0x1C;
+    private static final int PRESENCE_TEXT = 0x1D;
+    private static final int LAST_ACTIVITY = 0x1E;
+    private static final int CHAT_STATE = 0x1F;
+    private static final int FILTER_CONVO_ID = 0x20;
+    private static final int CONVO_LISTING_SIZE = 0x21;
+    private static final int FILTER_PRESENCE = 0x22;
+    private static final int FILTER_UID_PRESENT = 0x23;
+    private static final int CHAT_STATE_CONVO_ID = 0x24;
+    private static final int FOLDER_VER_COUNTER = 0x25;
+    private static final int FILTER_MESSAGE_HANDLE = 0x26;
+    private static final int NOTIFICATION_FILTER = 0x27;
+    private static final int CONVO_PARAMETER_MASK = 0x28;
 
     // Length defined for Application Parameters
-    private static final int MAX_LIST_COUNT_LEN       = 0x02; //, 0x0000, 0xFFFF),
-    private static final int START_OFFSET_LEN         = 0x02; //, 0x0000, 0xFFFF),
-    private static final int FILTER_MESSAGE_TYPE_LEN  = 0x01; //, 0x0000, 0x000f),
-    private static final int FILTER_READ_STATUS_LEN   = 0x01; //, 0x0000, 0x0002),
-    private static final int FILTER_PRIORITY_LEN      = 0x01; //, 0x0000, 0x0002),
-    private static final int ATTACHMENT_LEN           = 0x01; //, 0x0000, 0x0001),
-    private static final int TRANSPARENT_LEN          = 0x01; //, 0x0000, 0x0001),
-    private static final int RETRY_LEN                = 0x01; //, 0x0000, 0x0001),
-    private static final int NEW_MESSAGE_LEN          = 0x01; //, 0x0000, 0x0001),
-    private static final int NOTIFICATION_STATUS_LEN  = 0x01; //, 0x0000, 0xFFFF),
-    private static final int MAS_INSTANCE_ID_LEN      = 0x01; //, 0x0000, 0x00FF),
-    private static final int PARAMETER_MASK_LEN       = 0x04; //, 0x0000, 0x0000),
-    private static final int FOLDER_LISTING_SIZE_LEN  = 0x02; //, 0x0000, 0xFFFF),
+    private static final int MAX_LIST_COUNT_LEN = 0x02; //, 0x0000, 0xFFFF),
+    private static final int START_OFFSET_LEN = 0x02; //, 0x0000, 0xFFFF),
+    private static final int FILTER_MESSAGE_TYPE_LEN = 0x01; //, 0x0000, 0x000f),
+    private static final int FILTER_READ_STATUS_LEN = 0x01; //, 0x0000, 0x0002),
+    private static final int FILTER_PRIORITY_LEN = 0x01; //, 0x0000, 0x0002),
+    private static final int ATTACHMENT_LEN = 0x01; //, 0x0000, 0x0001),
+    private static final int TRANSPARENT_LEN = 0x01; //, 0x0000, 0x0001),
+    private static final int RETRY_LEN = 0x01; //, 0x0000, 0x0001),
+    private static final int NEW_MESSAGE_LEN = 0x01; //, 0x0000, 0x0001),
+    private static final int NOTIFICATION_STATUS_LEN = 0x01; //, 0x0000, 0xFFFF),
+    private static final int MAS_INSTANCE_ID_LEN = 0x01; //, 0x0000, 0x00FF),
+    private static final int PARAMETER_MASK_LEN = 0x04; //, 0x0000, 0x0000),
+    private static final int FOLDER_LISTING_SIZE_LEN = 0x02; //, 0x0000, 0xFFFF),
     private static final int MESSAGE_LISTING_SIZE_LEN = 0x02; //, 0x0000, 0xFFFF),
-    private static final int SUBJECT_LENGTH_LEN       = 0x01; //, 0x0000, 0x00FF),
-    private static final int CHARSET_LEN              = 0x01; //, 0x0000, 0x0001),
-    private static final int FRACTION_REQUEST_LEN     = 0x01; //, 0x0000, 0x0001),
-    private static final int FRACTION_DELIVER_LEN     = 0x01; //, 0x0000, 0x0001),
-    private static final int STATUS_INDICATOR_LEN     = 0x01; //, 0x0000, 0x0001),
-    private static final int STATUS_VALUE_LEN         = 0x01; //, 0x0000, 0x0001),
-    private static final int DATABASE_INDETIFIER_LEN  = 0x10;
+    private static final int SUBJECT_LENGTH_LEN = 0x01; //, 0x0000, 0x00FF),
+    private static final int CHARSET_LEN = 0x01; //, 0x0000, 0x0001),
+    private static final int FRACTION_REQUEST_LEN = 0x01; //, 0x0000, 0x0001),
+    private static final int FRACTION_DELIVER_LEN = 0x01; //, 0x0000, 0x0001),
+    private static final int STATUS_INDICATOR_LEN = 0x01; //, 0x0000, 0x0001),
+    private static final int STATUS_VALUE_LEN = 0x01; //, 0x0000, 0x0001),
+    private static final int DATABASE_INDETIFIER_LEN = 0x10;
     private static final int CONVO_LIST_VER_COUNTER_LEN = 0x10;
-    private static final int PRESENCE_AVAILABLE_LEN   = 0X01;
-    private static final int CHAT_STATE_LEN           = 0x01;
-    private static final int CHAT_STATE_CONVO_ID_LEN  = 0x10;
-    private static final int FILTER_CONVO_ID_LEN      = 0x20;
-    private static final int CONVO_LISTING_SIZE_LEN   = 0x02;
-    private static final int FILTER_PRESENCE_LEN      = 0x01;
-    private static final int FILTER_UID_PRESENT_LEN   = 0x01;
-    private static final int FOLDER_VER_COUNTER_LEN   = 0x10;
-    private static final int FILTER_MESSAGE_HANDLE_LEN= 0x10;
-    private static final int NOTIFICATION_FILTER_LEN  = 0x04;
+    private static final int PRESENCE_AVAILABLE_LEN = 0X01;
+    private static final int CHAT_STATE_LEN = 0x01;
+    private static final int CHAT_STATE_CONVO_ID_LEN = 0x10;
+    private static final int FILTER_CONVO_ID_LEN = 0x20;
+    private static final int CONVO_LISTING_SIZE_LEN = 0x02;
+    private static final int FILTER_PRESENCE_LEN = 0x01;
+    private static final int FILTER_UID_PRESENT_LEN = 0x01;
+    private static final int FOLDER_VER_COUNTER_LEN = 0x10;
+    private static final int FILTER_MESSAGE_HANDLE_LEN = 0x10;
+    private static final int NOTIFICATION_FILTER_LEN = 0x04;
     private static final int CONVO_PARAMETER_MASK_LEN = 0x04;
 
     // Default values
-    public static final int INVALID_VALUE_PARAMETER     =-1;
-    public static final int NOTIFICATION_STATUS_NO      = 0;
-    public static final int NOTIFICATION_STATUS_YES     = 1;
-    public static final int STATUS_INDICATOR_READ       = 0;
-    public static final int STATUS_INDICATOR_DELETED    = 1;
-    public static final int STATUS_VALUE_YES            = 1;
-    public static final int STATUS_VALUE_NO             = 0;
-    public static final int CHARSET_NATIVE              = 0;
-    public static final int CHARSET_UTF8                = 1;
-    public static final int FRACTION_REQUEST_FIRST      = 0;
-    public static final int FRACTION_REQUEST_NEXT       = 1;
-    public static final int FRACTION_DELIVER_MORE       = 0;
-    public static final int FRACTION_DELIVER_LAST       = 1;
+    public static final int INVALID_VALUE_PARAMETER = -1;
+    public static final int NOTIFICATION_STATUS_NO = 0;
+    public static final int NOTIFICATION_STATUS_YES = 1;
+    public static final int STATUS_INDICATOR_READ = 0;
+    public static final int STATUS_INDICATOR_DELETED = 1;
+    public static final int STATUS_VALUE_YES = 1;
+    public static final int STATUS_VALUE_NO = 0;
+    public static final int CHARSET_NATIVE = 0;
+    public static final int CHARSET_UTF8 = 1;
+    public static final int FRACTION_REQUEST_FIRST = 0;
+    public static final int FRACTION_REQUEST_NEXT = 1;
+    public static final int FRACTION_DELIVER_MORE = 0;
+    public static final int FRACTION_DELIVER_LAST = 1;
 
-    public static final int FILTER_NO_SMS_GSM    = 0x01;
-    public static final int FILTER_NO_SMS_CDMA   = 0x02;
-    public static final int FILTER_NO_EMAIL      = 0x04;
-    public static final int FILTER_NO_MMS        = 0x08;
-    public static final int FILTER_NO_IM         = 0x10;
+    public static final int FILTER_NO_SMS_GSM = 0x01;
+    public static final int FILTER_NO_SMS_CDMA = 0x02;
+    public static final int FILTER_NO_EMAIL = 0x04;
+    public static final int FILTER_NO_MMS = 0x08;
+    public static final int FILTER_NO_IM = 0x10;
     public static final int FILTER_MSG_TYPE_MASK = 0x1F;
 
-    private int mMaxListCount                   = INVALID_VALUE_PARAMETER;
-    private int mStartOffset                    = INVALID_VALUE_PARAMETER;
-    private int mFilterMessageType              = INVALID_VALUE_PARAMETER;
+    private int mMaxListCount = INVALID_VALUE_PARAMETER;
+    private int mStartOffset = INVALID_VALUE_PARAMETER;
+    private int mFilterMessageType = INVALID_VALUE_PARAMETER;
     // It seems like these are not implemented...
-    private long mFilterPeriodBegin             = INVALID_VALUE_PARAMETER;
-    private long mFilterPeriodEnd               = INVALID_VALUE_PARAMETER;
-    private int mFilterReadStatus               = INVALID_VALUE_PARAMETER;
-    private String mFilterRecipient             = null;
-    private String mFilterOriginator            = null;
-    private int mFilterPriority                 = INVALID_VALUE_PARAMETER;
-    private int mAttachment                     = INVALID_VALUE_PARAMETER;
-    private int mTransparent                    = INVALID_VALUE_PARAMETER;
-    private int mRetry                          = INVALID_VALUE_PARAMETER;
-    private int mNewMessage                     = INVALID_VALUE_PARAMETER;
-    private int mNotificationStatus             = INVALID_VALUE_PARAMETER;
-    private long mNotificationFilter            = INVALID_VALUE_PARAMETER;
-    private int mMasInstanceId                  = INVALID_VALUE_PARAMETER;
-    private long mParameterMask                 = INVALID_VALUE_PARAMETER;
-    private int mFolderListingSize              = INVALID_VALUE_PARAMETER;
-    private int mMessageListingSize             = INVALID_VALUE_PARAMETER;
-    private int mConvoListingSize               = INVALID_VALUE_PARAMETER;
-    private int mSubjectLength                  = INVALID_VALUE_PARAMETER;
-    private int mCharset                        = INVALID_VALUE_PARAMETER;
-    private int mFractionRequest                = INVALID_VALUE_PARAMETER;
-    private int mFractionDeliver                = INVALID_VALUE_PARAMETER;
-    private int mStatusIndicator                = INVALID_VALUE_PARAMETER;
-    private int mStatusValue                    = INVALID_VALUE_PARAMETER;
-    private long mMseTime                       = INVALID_VALUE_PARAMETER;
+    private long mFilterPeriodBegin = INVALID_VALUE_PARAMETER;
+    private long mFilterPeriodEnd = INVALID_VALUE_PARAMETER;
+    private int mFilterReadStatus = INVALID_VALUE_PARAMETER;
+    private String mFilterRecipient = null;
+    private String mFilterOriginator = null;
+    private int mFilterPriority = INVALID_VALUE_PARAMETER;
+    private int mAttachment = INVALID_VALUE_PARAMETER;
+    private int mTransparent = INVALID_VALUE_PARAMETER;
+    private int mRetry = INVALID_VALUE_PARAMETER;
+    private int mNewMessage = INVALID_VALUE_PARAMETER;
+    private int mNotificationStatus = INVALID_VALUE_PARAMETER;
+    private long mNotificationFilter = INVALID_VALUE_PARAMETER;
+    private int mMasInstanceId = INVALID_VALUE_PARAMETER;
+    private long mParameterMask = INVALID_VALUE_PARAMETER;
+    private int mFolderListingSize = INVALID_VALUE_PARAMETER;
+    private int mMessageListingSize = INVALID_VALUE_PARAMETER;
+    private int mConvoListingSize = INVALID_VALUE_PARAMETER;
+    private int mSubjectLength = INVALID_VALUE_PARAMETER;
+    private int mCharset = INVALID_VALUE_PARAMETER;
+    private int mFractionRequest = INVALID_VALUE_PARAMETER;
+    private int mFractionDeliver = INVALID_VALUE_PARAMETER;
+    private int mStatusIndicator = INVALID_VALUE_PARAMETER;
+    private int mStatusValue = INVALID_VALUE_PARAMETER;
+    private long mMseTime = INVALID_VALUE_PARAMETER;
     // TODO: Change to use SignedLongLong?
-    private long mConvoListingVerCounterLow     = INVALID_VALUE_PARAMETER;
-    private long mConvoListingVerCounterHigh    = INVALID_VALUE_PARAMETER;
-    private long mDatabaseIdentifierLow         = INVALID_VALUE_PARAMETER;
-    private long mDatabaseIdentifierHigh        = INVALID_VALUE_PARAMETER;
-    private long mFolderVerCounterLow           = INVALID_VALUE_PARAMETER;
-    private long mFolderVerCounterHigh          = INVALID_VALUE_PARAMETER;
-    private int mPresenceAvailability           = INVALID_VALUE_PARAMETER;
-    private String mPresenceStatus              = null;
-    private long mLastActivity                  = INVALID_VALUE_PARAMETER;
-    private int mChatState                      = INVALID_VALUE_PARAMETER;
-    private SignedLongLong mFilterConvoId       = null;
-    private int mFilterPresence                 = INVALID_VALUE_PARAMETER;
-    private int mFilterUidPresent               = INVALID_VALUE_PARAMETER;
-    private SignedLongLong mChatStateConvoId    = null;
-    private long mFilterMsgHandle               = INVALID_VALUE_PARAMETER;
-    private long mConvoParameterMask            = INVALID_VALUE_PARAMETER;
+    private long mConvoListingVerCounterLow = INVALID_VALUE_PARAMETER;
+    private long mConvoListingVerCounterHigh = INVALID_VALUE_PARAMETER;
+    private long mDatabaseIdentifierLow = INVALID_VALUE_PARAMETER;
+    private long mDatabaseIdentifierHigh = INVALID_VALUE_PARAMETER;
+    private long mFolderVerCounterLow = INVALID_VALUE_PARAMETER;
+    private long mFolderVerCounterHigh = INVALID_VALUE_PARAMETER;
+    private int mPresenceAvailability = INVALID_VALUE_PARAMETER;
+    private String mPresenceStatus = null;
+    private long mLastActivity = INVALID_VALUE_PARAMETER;
+    private int mChatState = INVALID_VALUE_PARAMETER;
+    private SignedLongLong mFilterConvoId = null;
+    private int mFilterPresence = INVALID_VALUE_PARAMETER;
+    private int mFilterUidPresent = INVALID_VALUE_PARAMETER;
+    private SignedLongLong mChatStateConvoId = null;
+    private long mFilterMsgHandle = INVALID_VALUE_PARAMETER;
+    private long mConvoParameterMask = INVALID_VALUE_PARAMETER;
 
     /**
      * Default constructor, used to build an application parameter object to be
@@ -204,8 +204,8 @@
      *             if a parameter string if formated incorrectly.
      */
     public BluetoothMapAppParams(final byte[] appParams)
-                 throws IllegalArgumentException, ParseException {
-        ParseParams(appParams);
+            throws IllegalArgumentException, ParseException {
+        parseParams(appParams);
     }
 
     /**
@@ -220,8 +220,8 @@
      * @throws ParseException
      *             if a parameter string if formated incorrectly.
      */
-    private void ParseParams(final byte[] appParams) throws ParseException,
-              IllegalArgumentException {
+    private void parseParams(final byte[] appParams)
+            throws ParseException, IllegalArgumentException {
         int i = 0;
         int tagId, tagLength;
         ByteBuffer appParamBuf = ByteBuffer.wrap(appParams);
@@ -230,328 +230,337 @@
             tagId = appParams[i++] & 0xff;     // Convert to unsigned to support values above 127
             tagLength = appParams[i++] & 0xff; // Convert to unsigned to support values above 127
             switch (tagId) {
-            case MAX_LIST_COUNT:
-                if (tagLength != MAX_LIST_COUNT_LEN) {
-                    Log.w(TAG, "MAX_LIST_COUNT: Wrong length received: " + tagLength
-                               + " expected: " + MAX_LIST_COUNT_LEN);
-                } else {
-                    setMaxListCount(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
-                }
-                break;
-            case START_OFFSET:
-                if (tagLength != START_OFFSET_LEN) {
-                    Log.w(TAG, "START_OFFSET: Wrong length received: " + tagLength + " expected: "
-                               + START_OFFSET_LEN);
-                } else {
-                    setStartOffset(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
-                }
-                break;
-            case FILTER_MESSAGE_TYPE:
-                if (tagLength != FILTER_MESSAGE_TYPE_LEN) {
-                    Log.w(TAG, "FILTER_MESSAGE_TYPE: Wrong length received: " + tagLength
-                            + " expected: " + FILTER_MESSAGE_TYPE_LEN);
-                } else {
-                    setFilterMessageType(appParams[i] & 0x1f);
-                }
-                break;
-            case FILTER_PERIOD_BEGIN:
-                if(tagLength != 0) {
-                    setFilterPeriodBegin(new String(appParams, i, tagLength));
-                } else {
-                    Log.w(TAG, "FILTER_PERIOD_BEGIN: Wrong length received: " + tagLength +
-                            " expected to be more than 0");
-                }
-                break;
-            case FILTER_PERIOD_END:
-                if(tagLength != 0) {
-                    setFilterPeriodEnd(new String(appParams, i, tagLength));
-                } else {
-                    Log.w(TAG, "FILTER_PERIOD_END: Wrong length received: " + tagLength +
-                            " expected to be more than 0");
-                }
-                break;
-            case FILTER_READ_STATUS:
-                if (tagLength != FILTER_READ_STATUS_LEN) {
-                     Log.w(TAG, "FILTER_READ_STATUS: Wrong length received: " + tagLength +
-                             " expected: " + FILTER_READ_STATUS_LEN);
-                } else {
-                    setFilterReadStatus(appParams[i] & 0x03); // Lower two bits
-                }
-                break;
-            case FILTER_RECIPIENT:
-                if(tagLength != 0) {
-                    setFilterRecipient(new String(appParams, i, tagLength));
-                } else {
-                    Log.w(TAG, "FILTER_RECIPIENT: Wrong length received: " + tagLength +
-                            " expected to be more than 0");
-                }
-                break;
-            case FILTER_ORIGINATOR:
-                if(tagLength != 0) {
-                    setFilterOriginator(new String(appParams, i, tagLength));
-                } else {
-                    Log.w(TAG, "FILTER_ORIGINATOR: Wrong length received: " + tagLength +
-                            " expected to be more than 0");
-                }
-                break;
-            case FILTER_PRIORITY:
-                if (tagLength != FILTER_PRIORITY_LEN) {
-                     Log.w(TAG, "FILTER_PRIORITY: Wrong length received: " + tagLength +
-                             " expected: " + FILTER_PRIORITY_LEN);
-                } else {
-                    setFilterPriority(appParams[i] & 0x03); // Lower two bits
-                }
-                break;
-            case ATTACHMENT:
-                if (tagLength != ATTACHMENT_LEN) {
-                     Log.w(TAG, "ATTACHMENT: Wrong length received: " + tagLength + " expected: "
-                             + ATTACHMENT_LEN);
-                } else {
-                    setAttachment(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case TRANSPARENT:
-                if (tagLength != TRANSPARENT_LEN) {
-                     Log.w(TAG, "TRANSPARENT: Wrong length received: " + tagLength + " expected: "
-                             + TRANSPARENT_LEN);
-                } else {
-                    setTransparent(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case RETRY:
-                if (tagLength != RETRY_LEN) {
-                     Log.w(TAG, "RETRY: Wrong length received: " + tagLength + " expected: "
-                             + RETRY_LEN);
-                } else {
-                    setRetry(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case NEW_MESSAGE:
-                if (tagLength != NEW_MESSAGE_LEN) {
-                     Log.w(TAG, "NEW_MESSAGE: Wrong length received: " + tagLength + " expected: "
-                             + NEW_MESSAGE_LEN);
-                } else {
-                    setNewMessage(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case NOTIFICATION_STATUS:
-                if (tagLength != NOTIFICATION_STATUS_LEN) {
-                     Log.w(TAG, "NOTIFICATION_STATUS: Wrong length received: " + tagLength +
-                             " expected: " + NOTIFICATION_STATUS_LEN);
-                } else {
-                    setNotificationStatus(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case NOTIFICATION_FILTER:
-                if (tagLength != NOTIFICATION_FILTER_LEN) {
-                     Log.w(TAG, "NOTIFICATION_FILTER: Wrong length received: " + tagLength +
-                             " expected: " + NOTIFICATION_FILTER_LEN);
-                } else {
-                    setNotificationFilter(appParamBuf.getInt(i) & 0xffffffffL); // 4 bytes
-                }
-                break;
-            case MAS_INSTANCE_ID:
-                if (tagLength != MAS_INSTANCE_ID_LEN) {
-                    Log.w(TAG, "MAS_INSTANCE_ID: Wrong length received: " + tagLength +
-                            " expected: " + MAS_INSTANCE_ID_LEN);
-                } else {
-                    setMasInstanceId(appParams[i] & 0xff);
-                }
-                break;
-            case PARAMETER_MASK:
-                if (tagLength != PARAMETER_MASK_LEN) {
-                    Log.w(TAG, "PARAMETER_MASK: Wrong length received: " + tagLength +
-                            " expected: " + PARAMETER_MASK_LEN);
-                } else {
-                    setParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
-                }
-                break;
-            case FOLDER_LISTING_SIZE:
-                if (tagLength != FOLDER_LISTING_SIZE_LEN) {
-                    Log.w(TAG, "FOLDER_LISTING_SIZE: Wrong length received: " + tagLength +
-                            " expected: " + FOLDER_LISTING_SIZE_LEN);
-                } else {
-                    setFolderListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
-                }
-                break;
-            case MESSAGE_LISTING_SIZE:
-                if (tagLength != MESSAGE_LISTING_SIZE_LEN) {
-                    Log.w(TAG, "MESSAGE_LISTING_SIZE: Wrong length received: " + tagLength +
-                            " expected: " + MESSAGE_LISTING_SIZE_LEN);
-                } else {
-                    setMessageListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
-                }
-                break;
-            case SUBJECT_LENGTH:
-                if (tagLength != SUBJECT_LENGTH_LEN) {
-                    Log.w(TAG, "SUBJECT_LENGTH: Wrong length received: " + tagLength +
-                            " expected: " + SUBJECT_LENGTH_LEN);
-                } else {
-                    setSubjectLength(appParams[i] & 0xff);
-                }
-                break;
-            case CHARSET:
-                if (tagLength != CHARSET_LEN) {
-                    Log.w(TAG, "CHARSET: Wrong length received: " + tagLength + " expected: "
-                            + CHARSET_LEN);
-                } else {
-                    setCharset(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case FRACTION_REQUEST:
-                if (tagLength != FRACTION_REQUEST_LEN) {
-                    Log.w(TAG, "FRACTION_REQUEST: Wrong length received: " + tagLength +
-                            " expected: " + FRACTION_REQUEST_LEN);
-                } else {
-                    setFractionRequest(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case FRACTION_DELIVER:
-                if (tagLength != FRACTION_DELIVER_LEN) {
-                    Log.w(TAG, "FRACTION_DELIVER: Wrong length received: " + tagLength +
-                            " expected: " + FRACTION_DELIVER_LEN);
-                } else {
-                    setFractionDeliver(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case STATUS_INDICATOR:
-                if (tagLength != STATUS_INDICATOR_LEN) {
-                    Log.w(TAG, "STATUS_INDICATOR: Wrong length received: " + tagLength +
-                            " expected: " + STATUS_INDICATOR_LEN);
-                } else {
-                    setStatusIndicator(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case STATUS_VALUE:
-                if (tagLength != STATUS_VALUE_LEN) {
-                    Log.w(TAG, "STATUS_VALUER: Wrong length received: " + tagLength + " expected: "
-                            + STATUS_VALUE_LEN);
-                } else {
-                    setStatusValue(appParams[i] & 0x01); // Lower bit
-                }
-                break;
-            case MSE_TIME:
-                setMseTime(new String(appParams, i, tagLength));
-                break;
-            case DATABASE_INDETIFIER:
-                if((tagLength != DATABASE_INDETIFIER_LEN)){
-                    Log.w(TAG, "DATABASE_IDENTIFIER: Wrong length received: " + tagLength +
-                            " expected: " + DATABASE_INDETIFIER_LEN);
-                } else {
-                    setDatabaseIdentifier(appParamBuf.getLong(i)/*MSB*/,
-                            appParamBuf.getLong(i+8)/*LSB*/);
-                }
-                break;
-            case CONVO_LIST_VER_COUNTER:
-                if((tagLength != CONVO_LIST_VER_COUNTER_LEN)){
-                    Log.w(TAG, "CONVO_LIST_VER_COUNTER: Wrong length received: " + tagLength +
-                            " expected: "  + CONVO_LIST_VER_COUNTER_LEN);
-                } else {
-                    setConvoListingVerCounter(appParamBuf.getLong(i)/*MSB*/,
-                            appParamBuf.getLong(i+8)/*LSB*/);
-                }
-                break;
-            case PRESENCE_AVAILABLE:
-                if((tagLength != PRESENCE_AVAILABLE_LEN)){
-                    Log.w(TAG, "PRESENCE_AVAILABLE: Wrong length received: " + tagLength +
-                            " expected: "  + PRESENCE_AVAILABLE_LEN);
-                } else {
-                    setPresenceAvailability(appParams[i]);
-                }
-                break;
-            case PRESENCE_TEXT:
-                if(tagLength != 0) {
-                    setPresenceStatus(new String(appParams, i, tagLength));
-                } else
-                    Log.w(TAG, "PRESENCE_STATUS: Wrong length received: " + tagLength +
-                            " expected to be more than 0");
-                break;
-            case LAST_ACTIVITY:
-                if(tagLength != 0) {
-                    setLastActivity(new String(appParams, i, tagLength));
-                } else
-                    Log.w(TAG, "LAST_ACTIVITY: Wrong length received: " + tagLength +
-                            " expected to be more than 0");
-                break;
-            case CHAT_STATE:
-                if((tagLength != CHAT_STATE_LEN)){
-                    Log.w(TAG, "CHAT_STATE: Wrong length received: " + tagLength +
-                            " expected: "  + CHAT_STATE_LEN);
-                } else {
-                    setChatState(appParams[i]);
-                }
-                break;
-            case FILTER_CONVO_ID:
-                if((tagLength != 0) && (tagLength <= FILTER_CONVO_ID_LEN)){
-                    setFilterConvoId(new String(appParams, i, tagLength));
-                } else {
-                    Log.w(TAG, "FILTER_CONVO_ID: Wrong length received: " + tagLength +
-                        " expected: "  + FILTER_CONVO_ID_LEN);
-                }
-                break;
-            case CONVO_LISTING_SIZE:
-                if(tagLength != CONVO_LISTING_SIZE_LEN){
-                    Log.w(TAG, "LISTING_SIZE: Wrong length received: "+ tagLength+" expected: "+
-                            CONVO_LISTING_SIZE_LEN);
+                case MAX_LIST_COUNT:
+                    if (tagLength != MAX_LIST_COUNT_LEN) {
+                        Log.w(TAG, "MAX_LIST_COUNT: Wrong length received: " + tagLength
+                                + " expected: " + MAX_LIST_COUNT_LEN);
+                    } else {
+                        setMaxListCount(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
+                    }
+                    break;
+                case START_OFFSET:
+                    if (tagLength != START_OFFSET_LEN) {
+                        Log.w(TAG,
+                                "START_OFFSET: Wrong length received: " + tagLength + " expected: "
+                                        + START_OFFSET_LEN);
+                    } else {
+                        setStartOffset(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
+                    }
+                    break;
+                case FILTER_MESSAGE_TYPE:
+                    if (tagLength != FILTER_MESSAGE_TYPE_LEN) {
+                        Log.w(TAG, "FILTER_MESSAGE_TYPE: Wrong length received: " + tagLength
+                                + " expected: " + FILTER_MESSAGE_TYPE_LEN);
+                    } else {
+                        setFilterMessageType(appParams[i] & 0x1f);
+                    }
+                    break;
+                case FILTER_PERIOD_BEGIN:
+                    if (tagLength != 0) {
+                        setFilterPeriodBegin(new String(appParams, i, tagLength));
+                    } else {
+                        Log.w(TAG, "FILTER_PERIOD_BEGIN: Wrong length received: " + tagLength
+                                + " expected to be more than 0");
+                    }
+                    break;
+                case FILTER_PERIOD_END:
+                    if (tagLength != 0) {
+                        setFilterPeriodEnd(new String(appParams, i, tagLength));
+                    } else {
+                        Log.w(TAG, "FILTER_PERIOD_END: Wrong length received: " + tagLength
+                                + " expected to be more than 0");
+                    }
+                    break;
+                case FILTER_READ_STATUS:
+                    if (tagLength != FILTER_READ_STATUS_LEN) {
+                        Log.w(TAG, "FILTER_READ_STATUS: Wrong length received: " + tagLength
+                                + " expected: " + FILTER_READ_STATUS_LEN);
+                    } else {
+                        setFilterReadStatus(appParams[i] & 0x03); // Lower two bits
+                    }
+                    break;
+                case FILTER_RECIPIENT:
+                    if (tagLength != 0) {
+                        setFilterRecipient(new String(appParams, i, tagLength));
+                    } else {
+                        Log.w(TAG, "FILTER_RECIPIENT: Wrong length received: " + tagLength
+                                + " expected to be more than 0");
+                    }
+                    break;
+                case FILTER_ORIGINATOR:
+                    if (tagLength != 0) {
+                        setFilterOriginator(new String(appParams, i, tagLength));
+                    } else {
+                        Log.w(TAG, "FILTER_ORIGINATOR: Wrong length received: " + tagLength
+                                + " expected to be more than 0");
+                    }
+                    break;
+                case FILTER_PRIORITY:
+                    if (tagLength != FILTER_PRIORITY_LEN) {
+                        Log.w(TAG, "FILTER_PRIORITY: Wrong length received: " + tagLength
+                                + " expected: " + FILTER_PRIORITY_LEN);
+                    } else {
+                        setFilterPriority(appParams[i] & 0x03); // Lower two bits
+                    }
+                    break;
+                case ATTACHMENT:
+                    if (tagLength != ATTACHMENT_LEN) {
+                        Log.w(TAG, "ATTACHMENT: Wrong length received: " + tagLength + " expected: "
+                                + ATTACHMENT_LEN);
+                    } else {
+                        setAttachment(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case TRANSPARENT:
+                    if (tagLength != TRANSPARENT_LEN) {
+                        Log.w(TAG,
+                                "TRANSPARENT: Wrong length received: " + tagLength + " expected: "
+                                        + TRANSPARENT_LEN);
+                    } else {
+                        setTransparent(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case RETRY:
+                    if (tagLength != RETRY_LEN) {
+                        Log.w(TAG, "RETRY: Wrong length received: " + tagLength + " expected: "
+                                + RETRY_LEN);
+                    } else {
+                        setRetry(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case NEW_MESSAGE:
+                    if (tagLength != NEW_MESSAGE_LEN) {
+                        Log.w(TAG,
+                                "NEW_MESSAGE: Wrong length received: " + tagLength + " expected: "
+                                        + NEW_MESSAGE_LEN);
+                    } else {
+                        setNewMessage(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case NOTIFICATION_STATUS:
+                    if (tagLength != NOTIFICATION_STATUS_LEN) {
+                        Log.w(TAG, "NOTIFICATION_STATUS: Wrong length received: " + tagLength
+                                + " expected: " + NOTIFICATION_STATUS_LEN);
+                    } else {
+                        setNotificationStatus(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case NOTIFICATION_FILTER:
+                    if (tagLength != NOTIFICATION_FILTER_LEN) {
+                        Log.w(TAG, "NOTIFICATION_FILTER: Wrong length received: " + tagLength
+                                + " expected: " + NOTIFICATION_FILTER_LEN);
+                    } else {
+                        setNotificationFilter(appParamBuf.getInt(i) & 0xffffffffL); // 4 bytes
+                    }
+                    break;
+                case MAS_INSTANCE_ID:
+                    if (tagLength != MAS_INSTANCE_ID_LEN) {
+                        Log.w(TAG, "MAS_INSTANCE_ID: Wrong length received: " + tagLength
+                                + " expected: " + MAS_INSTANCE_ID_LEN);
+                    } else {
+                        setMasInstanceId(appParams[i] & 0xff);
+                    }
+                    break;
+                case PARAMETER_MASK:
+                    if (tagLength != PARAMETER_MASK_LEN) {
+                        Log.w(TAG, "PARAMETER_MASK: Wrong length received: " + tagLength
+                                + " expected: " + PARAMETER_MASK_LEN);
+                    } else {
+                        setParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
+                    }
+                    break;
+                case FOLDER_LISTING_SIZE:
+                    if (tagLength != FOLDER_LISTING_SIZE_LEN) {
+                        Log.w(TAG, "FOLDER_LISTING_SIZE: Wrong length received: " + tagLength
+                                + " expected: " + FOLDER_LISTING_SIZE_LEN);
+                    } else {
+                        setFolderListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
+                    }
+                    break;
+                case MESSAGE_LISTING_SIZE:
+                    if (tagLength != MESSAGE_LISTING_SIZE_LEN) {
+                        Log.w(TAG, "MESSAGE_LISTING_SIZE: Wrong length received: " + tagLength
+                                + " expected: " + MESSAGE_LISTING_SIZE_LEN);
+                    } else {
+                        setMessageListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned
+                    }
+                    break;
+                case SUBJECT_LENGTH:
+                    if (tagLength != SUBJECT_LENGTH_LEN) {
+                        Log.w(TAG, "SUBJECT_LENGTH: Wrong length received: " + tagLength
+                                + " expected: " + SUBJECT_LENGTH_LEN);
+                    } else {
+                        setSubjectLength(appParams[i] & 0xff);
+                    }
+                    break;
+                case CHARSET:
+                    if (tagLength != CHARSET_LEN) {
+                        Log.w(TAG, "CHARSET: Wrong length received: " + tagLength + " expected: "
+                                + CHARSET_LEN);
+                    } else {
+                        setCharset(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case FRACTION_REQUEST:
+                    if (tagLength != FRACTION_REQUEST_LEN) {
+                        Log.w(TAG, "FRACTION_REQUEST: Wrong length received: " + tagLength
+                                + " expected: " + FRACTION_REQUEST_LEN);
+                    } else {
+                        setFractionRequest(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case FRACTION_DELIVER:
+                    if (tagLength != FRACTION_DELIVER_LEN) {
+                        Log.w(TAG, "FRACTION_DELIVER: Wrong length received: " + tagLength
+                                + " expected: " + FRACTION_DELIVER_LEN);
+                    } else {
+                        setFractionDeliver(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case STATUS_INDICATOR:
+                    if (tagLength != STATUS_INDICATOR_LEN) {
+                        Log.w(TAG, "STATUS_INDICATOR: Wrong length received: " + tagLength
+                                + " expected: " + STATUS_INDICATOR_LEN);
+                    } else {
+                        setStatusIndicator(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case STATUS_VALUE:
+                    if (tagLength != STATUS_VALUE_LEN) {
+                        Log.w(TAG,
+                                "STATUS_VALUER: Wrong length received: " + tagLength + " expected: "
+                                        + STATUS_VALUE_LEN);
+                    } else {
+                        setStatusValue(appParams[i] & 0x01); // Lower bit
+                    }
+                    break;
+                case MSE_TIME:
+                    setMseTime(new String(appParams, i, tagLength));
+                    break;
+                case DATABASE_INDETIFIER:
+                    if ((tagLength != DATABASE_INDETIFIER_LEN)) {
+                        Log.w(TAG, "DATABASE_IDENTIFIER: Wrong length received: " + tagLength
+                                + " expected: " + DATABASE_INDETIFIER_LEN);
+                    } else {
+                        setDatabaseIdentifier(appParamBuf.getLong(i)/*MSB*/,
+                                appParamBuf.getLong(i + 8)/*LSB*/);
+                    }
+                    break;
+                case CONVO_LIST_VER_COUNTER:
+                    if ((tagLength != CONVO_LIST_VER_COUNTER_LEN)) {
+                        Log.w(TAG, "CONVO_LIST_VER_COUNTER: Wrong length received: " + tagLength
+                                + " expected: " + CONVO_LIST_VER_COUNTER_LEN);
+                    } else {
+                        setConvoListingVerCounter(appParamBuf.getLong(i)/*MSB*/,
+                                appParamBuf.getLong(i + 8)/*LSB*/);
+                    }
+                    break;
+                case PRESENCE_AVAILABLE:
+                    if ((tagLength != PRESENCE_AVAILABLE_LEN)) {
+                        Log.w(TAG, "PRESENCE_AVAILABLE: Wrong length received: " + tagLength
+                                + " expected: " + PRESENCE_AVAILABLE_LEN);
+                    } else {
+                        setPresenceAvailability(appParams[i]);
+                    }
+                    break;
+                case PRESENCE_TEXT:
+                    if (tagLength != 0) {
+                        setPresenceStatus(new String(appParams, i, tagLength));
+                    } else {
+                        Log.w(TAG, "PRESENCE_STATUS: Wrong length received: " + tagLength
+                                + " expected to be more than 0");
+                    }
+                    break;
+                case LAST_ACTIVITY:
+                    if (tagLength != 0) {
+                        setLastActivity(new String(appParams, i, tagLength));
+                    } else {
+                        Log.w(TAG, "LAST_ACTIVITY: Wrong length received: " + tagLength
+                                + " expected to be more than 0");
+                    }
+                    break;
+                case CHAT_STATE:
+                    if ((tagLength != CHAT_STATE_LEN)) {
+                        Log.w(TAG, "CHAT_STATE: Wrong length received: " + tagLength + " expected: "
+                                + CHAT_STATE_LEN);
+                    } else {
+                        setChatState(appParams[i]);
+                    }
+                    break;
+                case FILTER_CONVO_ID:
+                    if ((tagLength != 0) && (tagLength <= FILTER_CONVO_ID_LEN)) {
+                        setFilterConvoId(new String(appParams, i, tagLength));
+                    } else {
+                        Log.w(TAG, "FILTER_CONVO_ID: Wrong length received: " + tagLength
+                                + " expected: " + FILTER_CONVO_ID_LEN);
+                    }
+                    break;
+                case CONVO_LISTING_SIZE:
+                    if (tagLength != CONVO_LISTING_SIZE_LEN) {
+                        Log.w(TAG,
+                                "LISTING_SIZE: Wrong length received: " + tagLength + " expected: "
+                                        + CONVO_LISTING_SIZE_LEN);
 
-                } else {
-                    setConvoListingSize(appParamBuf.getShort(i) & 0xffff);
-                }
-                break;
-            case FILTER_PRESENCE:
-                if((tagLength != FILTER_PRESENCE_LEN)){
-                    Log.w(TAG, "FILTER_PRESENCE: Wrong length received: " + tagLength +
-                            " expected: "  + FILTER_PRESENCE_LEN);
-                } else {
-                    setFilterPresence(appParams[i]);
-                }
-                break;
-            case FILTER_UID_PRESENT:
-                if((tagLength != FILTER_UID_PRESENT_LEN)){
-                    Log.w(TAG, "FILTER_UID_PRESENT: Wrong length received: " + tagLength +
-                            " expected: "  + FILTER_UID_PRESENT_LEN);
-                } else {
-                    setFilterUidPresent(appParams[i]&0x1);
-                }
-                break;
-            case CHAT_STATE_CONVO_ID:
-                if((tagLength != CHAT_STATE_CONVO_ID_LEN)){
-                    Log.w(TAG, "CHAT_STATE_CONVO_ID: Wrong length received: " + tagLength +
-                            " expected: "  + CHAT_STATE_CONVO_ID_LEN);
-                } else {
+                    } else {
+                        setConvoListingSize(appParamBuf.getShort(i) & 0xffff);
+                    }
+                    break;
+                case FILTER_PRESENCE:
+                    if ((tagLength != FILTER_PRESENCE_LEN)) {
+                        Log.w(TAG, "FILTER_PRESENCE: Wrong length received: " + tagLength
+                                + " expected: " + FILTER_PRESENCE_LEN);
+                    } else {
+                        setFilterPresence(appParams[i]);
+                    }
+                    break;
+                case FILTER_UID_PRESENT:
+                    if ((tagLength != FILTER_UID_PRESENT_LEN)) {
+                        Log.w(TAG, "FILTER_UID_PRESENT: Wrong length received: " + tagLength
+                                + " expected: " + FILTER_UID_PRESENT_LEN);
+                    } else {
+                        setFilterUidPresent(appParams[i] & 0x1);
+                    }
+                    break;
+                case CHAT_STATE_CONVO_ID:
+                    if ((tagLength != CHAT_STATE_CONVO_ID_LEN)) {
+                        Log.w(TAG, "CHAT_STATE_CONVO_ID: Wrong length received: " + tagLength
+                                + " expected: " + CHAT_STATE_CONVO_ID_LEN);
+                    } else {
                     /* TODO: Is this correct convoId handling? */
-                    setChatStateConvoId(appParamBuf.getLong(i)/*MSB*/,
-                                        appParamBuf.getLong(i+8)/*LSB*/);
-                    Log.d(TAG, "CHAT_STATE_CONVO_ID: convo id " +
-                        "MSB=" + BluetoothMapUtils.getLongAsString(appParamBuf.getLong(i)) +
-                        ", LSB(+8)=" + BluetoothMapUtils.getLongAsString(appParamBuf.getLong(i+8)));
+                        setChatStateConvoId(appParamBuf.getLong(i)/*MSB*/,
+                                appParamBuf.getLong(i + 8)/*LSB*/);
+                        Log.d(TAG, "CHAT_STATE_CONVO_ID: convo id " + "MSB="
+                                + BluetoothMapUtils.getLongAsString(appParamBuf.getLong(i))
+                                + ", LSB(+8)=" + BluetoothMapUtils.getLongAsString(
+                                appParamBuf.getLong(i + 8)));
 
-                }
-                break;
-            case FOLDER_VER_COUNTER:
-                break;
-            case FILTER_MESSAGE_HANDLE:
-                if((tagLength != 0 && tagLength <= FILTER_MESSAGE_HANDLE_LEN)){
-                    setFilterMsgHandle(new String(appParams, i, tagLength));
-                } else {
-                    Log.w(TAG, "FILTER_MESSAGE_HANDLE: Wrong length received: " + tagLength +
-                            " expected: "  + FILTER_MESSAGE_HANDLE_LEN);
-                }
+                    }
+                    break;
+                case FOLDER_VER_COUNTER:
+                    break;
+                case FILTER_MESSAGE_HANDLE:
+                    if ((tagLength != 0 && tagLength <= FILTER_MESSAGE_HANDLE_LEN)) {
+                        setFilterMsgHandle(new String(appParams, i, tagLength));
+                    } else {
+                        Log.w(TAG, "FILTER_MESSAGE_HANDLE: Wrong length received: " + tagLength
+                                + " expected: " + FILTER_MESSAGE_HANDLE_LEN);
+                    }
 
-                break;
-            case CONVO_PARAMETER_MASK:
-                if (tagLength != CONVO_PARAMETER_MASK_LEN) {
-                    Log.w(TAG, "CONVO_PARAMETER_MASK: Wrong length received: " + tagLength +
-                            " expected: " + CONVO_PARAMETER_MASK_LEN);
-                } else {
-                    setConvoParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
-                }
-                break;
-            default:
-                // Just skip unknown Tags, no need to report error
-                Log.w(TAG, "Unknown TagId received ( 0x" + Integer.toString(tagId, 16)
-                           + "), skipping...");
-                break;
+                    break;
+                case CONVO_PARAMETER_MASK:
+                    if (tagLength != CONVO_PARAMETER_MASK_LEN) {
+                        Log.w(TAG, "CONVO_PARAMETER_MASK: Wrong length received: " + tagLength
+                                + " expected: " + CONVO_PARAMETER_MASK_LEN);
+                    } else {
+                        setConvoParameterMask(
+                                appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned
+                    }
+                    break;
+                default:
+                    // Just skip unknown Tags, no need to report error
+                    Log.w(TAG, "Unknown TagId received ( 0x" + Integer.toString(tagId, 16)
+                            + "), skipping...");
+                    break;
             }
             i += tagLength; // Offset to next TagId
         }
@@ -568,16 +577,19 @@
     private int getParamMaxLength() throws UnsupportedEncodingException {
         int length = 0;
         length += 38 * 2; // tagId + tagLength
-        length += 33+4*16; // fixed sizes TODO: Update when spec is ready
+        length += 33 + 4 * 16; // fixed sizes TODO: Update when spec is ready
         length += getFilterPeriodBegin() == INVALID_VALUE_PARAMETER ? 0 : 20;
         length += getFilterPeriodEnd() == INVALID_VALUE_PARAMETER ? 0 : 20;
-        if (getFilterRecipient() != null)
+        if (getFilterRecipient() != null) {
             length += getFilterRecipient().getBytes("UTF-8").length;
-        if (getFilterOriginator() != null)
+        }
+        if (getFilterOriginator() != null) {
             length += getFilterOriginator().getBytes("UTF-8").length;
+        }
         length += getMseTime() == INVALID_VALUE_PARAMETER ? 0 : 20;
-        if(getPresenceStatus() != null)
+        if (getPresenceStatus() != null) {
             length += getPresenceStatus().getBytes("UTF-8").length;
+        }
         length += (getLastActivity() == INVALID_VALUE_PARAMETER) ? 0 : 20;
         return length;
     }
@@ -589,7 +601,7 @@
      * @throws UnsupportedEncodingException
      *             if the platform does not support UTF-8 encoding.
      */
-    public byte[] EncodeParams() throws UnsupportedEncodingException {
+    public byte[] encodeParams() throws UnsupportedEncodingException {
         ByteBuffer appParamBuf = ByteBuffer.allocate(getParamMaxLength());
         appParamBuf.order(ByteOrder.BIG_ENDIAN);
         byte[] retBuf;
@@ -726,70 +738,70 @@
         }
         // Note: New for IM
         if (getDatabaseIdentifier() != null) {
-            appParamBuf.put((byte)DATABASE_INDETIFIER);
-            appParamBuf.put((byte)DATABASE_INDETIFIER_LEN);
+            appParamBuf.put((byte) DATABASE_INDETIFIER);
+            appParamBuf.put((byte) DATABASE_INDETIFIER_LEN);
             appParamBuf.put(getDatabaseIdentifier());
         }
         if (getConvoListingVerCounter() != null) {
-            appParamBuf.put((byte)CONVO_LIST_VER_COUNTER);
-            appParamBuf.put((byte)CONVO_LIST_VER_COUNTER_LEN);
+            appParamBuf.put((byte) CONVO_LIST_VER_COUNTER);
+            appParamBuf.put((byte) CONVO_LIST_VER_COUNTER_LEN);
             appParamBuf.put(getConvoListingVerCounter());
         }
         if (getPresenceAvailability() != INVALID_VALUE_PARAMETER) {
-            appParamBuf.put((byte)PRESENCE_AVAILABLE);
-            appParamBuf.put((byte)PRESENCE_AVAILABLE_LEN);
-            appParamBuf.putInt((int)getPresenceAvailability());
+            appParamBuf.put((byte) PRESENCE_AVAILABLE);
+            appParamBuf.put((byte) PRESENCE_AVAILABLE_LEN);
+            appParamBuf.putInt((int) getPresenceAvailability());
         }
-        if (getPresenceStatus()!= null) {
-            appParamBuf.put((byte)PRESENCE_TEXT);
-            appParamBuf.put((byte)getPresenceStatus().getBytes("UTF-8").length);
+        if (getPresenceStatus() != null) {
+            appParamBuf.put((byte) PRESENCE_TEXT);
+            appParamBuf.put((byte) getPresenceStatus().getBytes("UTF-8").length);
             appParamBuf.put(getPresenceStatus().getBytes());
         }
         if (getLastActivity() != INVALID_VALUE_PARAMETER) {
-            appParamBuf.put((byte)LAST_ACTIVITY);
-            appParamBuf.put((byte)getLastActivityString().getBytes("UTF-8").length);
+            appParamBuf.put((byte) LAST_ACTIVITY);
+            appParamBuf.put((byte) getLastActivityString().getBytes("UTF-8").length);
             appParamBuf.put(getLastActivityString().getBytes());
         }
         if (getChatState() != INVALID_VALUE_PARAMETER) {
-            appParamBuf.put((byte)CHAT_STATE);
-            appParamBuf.put((byte)CHAT_STATE_LEN);
-            appParamBuf.putShort((short)getChatState());
+            appParamBuf.put((byte) CHAT_STATE);
+            appParamBuf.put((byte) CHAT_STATE_LEN);
+            appParamBuf.putShort((short) getChatState());
         }
         if (getFilterConvoId() != null) {
-            appParamBuf.put((byte)FILTER_CONVO_ID);
-            appParamBuf.put((byte)FILTER_CONVO_ID_LEN);
+            appParamBuf.put((byte) FILTER_CONVO_ID);
+            appParamBuf.put((byte) FILTER_CONVO_ID_LEN);
             appParamBuf.putLong(getFilterConvoId().getMostSignificantBits());
             appParamBuf.putLong(getFilterConvoId().getLeastSignificantBits());
         }
         if (getConvoListingSize() != INVALID_VALUE_PARAMETER) {
-            appParamBuf.put((byte)CONVO_LISTING_SIZE);
-            appParamBuf.put((byte)CONVO_LISTING_SIZE_LEN);
-            appParamBuf.putShort((short)getConvoListingSize());
+            appParamBuf.put((byte) CONVO_LISTING_SIZE);
+            appParamBuf.put((byte) CONVO_LISTING_SIZE_LEN);
+            appParamBuf.putShort((short) getConvoListingSize());
         }
         if (getFilterPresence() != INVALID_VALUE_PARAMETER) {
-            appParamBuf.put((byte)FILTER_PRESENCE);
-            appParamBuf.put((byte)FILTER_PRESENCE_LEN);
-            appParamBuf.putShort((short)getFilterPresence());
+            appParamBuf.put((byte) FILTER_PRESENCE);
+            appParamBuf.put((byte) FILTER_PRESENCE_LEN);
+            appParamBuf.putShort((short) getFilterPresence());
         }
         if (getFilterUidPresent() != INVALID_VALUE_PARAMETER) {
-            appParamBuf.put((byte)FILTER_UID_PRESENT);
-            appParamBuf.put((byte)FILTER_UID_PRESENT_LEN);
-            appParamBuf.putShort((short)getFilterUidPresent());
+            appParamBuf.put((byte) FILTER_UID_PRESENT);
+            appParamBuf.put((byte) FILTER_UID_PRESENT_LEN);
+            appParamBuf.putShort((short) getFilterUidPresent());
         }
         if (getChatStateConvoId() != null) {
-            appParamBuf.put((byte)CHAT_STATE_CONVO_ID);
-            appParamBuf.put((byte)CHAT_STATE_CONVO_ID_LEN);
+            appParamBuf.put((byte) CHAT_STATE_CONVO_ID);
+            appParamBuf.put((byte) CHAT_STATE_CONVO_ID_LEN);
             appParamBuf.putLong(getChatStateConvoId().getMostSignificantBits());
             appParamBuf.putLong(getChatStateConvoId().getLeastSignificantBits());
         }
         if (getFolderVerCounter() != null) {
-            appParamBuf.put((byte)FOLDER_VER_COUNTER);
-            appParamBuf.put((byte)FOLDER_VER_COUNTER_LEN);
+            appParamBuf.put((byte) FOLDER_VER_COUNTER);
+            appParamBuf.put((byte) FOLDER_VER_COUNTER_LEN);
             appParamBuf.put(getFolderVerCounter());
         }
         if (getFilterMsgHandle() != INVALID_VALUE_PARAMETER) {
-            appParamBuf.put((byte)FILTER_MESSAGE_HANDLE);
-            appParamBuf.put((byte)FILTER_MESSAGE_HANDLE_LEN);
+            appParamBuf.put((byte) FILTER_MESSAGE_HANDLE);
+            appParamBuf.put((byte) FILTER_MESSAGE_HANDLE_LEN);
             appParamBuf.putLong(getFilterMsgHandle());
         }
         if (getConvoParameterMask() != INVALID_VALUE_PARAMETER) {
@@ -800,7 +812,7 @@
 
         // We need to reduce the length of the array to match the content
         retBuf = Arrays.copyOfRange(appParamBuf.array(), appParamBuf.arrayOffset(),
-                                    appParamBuf.arrayOffset() + appParamBuf.position());
+                appParamBuf.arrayOffset() + appParamBuf.position());
         return retBuf;
     }
 
@@ -809,8 +821,9 @@
     }
 
     public void setMaxListCount(int maxListCount) throws IllegalArgumentException {
-        if (maxListCount < 0 || maxListCount > 0xFFFF)
+        if (maxListCount < 0 || maxListCount > 0xFFFF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        }
         this.mMaxListCount = maxListCount;
     }
 
@@ -819,8 +832,9 @@
     }
 
     public void setStartOffset(int startOffset) throws IllegalArgumentException {
-        if (startOffset < 0 || startOffset > 0xFFFF)
+        if (startOffset < 0 || startOffset > 0xFFFF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        }
         this.mStartOffset = startOffset;
     }
 
@@ -829,8 +843,9 @@
     }
 
     public void setFilterMessageType(int filterMessageType) throws IllegalArgumentException {
-        if (filterMessageType < 0 || filterMessageType > 0x001F)
+        if (filterMessageType < 0 || filterMessageType > 0x001F) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x001F");
+        }
         this.mFilterMessageType = filterMessageType;
     }
 
@@ -857,23 +872,27 @@
     public long getFilterLastActivityBegin() {
         return mFilterPeriodBegin;
     }
+
     public String getFilterLastActivityBeginString() {
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
         Date date = new Date(mFilterPeriodBegin);
         return format.format(date); // Format to YYYYMMDDTHHMMSS local time
     }
+
     public void setFilterLastActivityBegin(long filterPeriodBegin) {
         this.mFilterPeriodBegin = filterPeriodBegin;
     }
 
-    public void setFilterLastActivityBegin(String filterPeriodBegin)throws ParseException {
+    public void setFilterLastActivityBegin(String filterPeriodBegin) throws ParseException {
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
         Date date = format.parse(filterPeriodBegin);
         this.mFilterPeriodBegin = date.getTime();
     }
+
     public long getFilterPeriodEnd() {
         return mFilterPeriodEnd;
     }
+
     public long getFilterLastActivityEnd() {
         return mFilterPeriodEnd;
     }
@@ -885,7 +904,7 @@
     }
 
     public void setFilterLastActivityEnd(long filterPeriodEnd) {
-        this.mFilterPeriodEnd= filterPeriodEnd; //er reuse the same
+        this.mFilterPeriodEnd = filterPeriodEnd; //er reuse the same
     }
 
     public void setFilterPeriodEnd(String filterPeriodEnd) throws ParseException {
@@ -893,6 +912,7 @@
         Date date = format.parse(filterPeriodEnd);
         this.mFilterPeriodEnd = date.getTime();
     }
+
     public String getFilterPeriodEndString() {
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
         Date date = new Date(mFilterPeriodEnd);
@@ -908,13 +928,15 @@
         Date date = format.parse(filterPeriodEnd);
         this.mFilterPeriodEnd = date.getTime();
     }
+
     public int getFilterReadStatus() {
         return mFilterReadStatus;
     }
 
     public void setFilterReadStatus(int filterReadStatus) throws IllegalArgumentException {
-        if (filterReadStatus < 0 || filterReadStatus > 0x0002)
+        if (filterReadStatus < 0 || filterReadStatus > 0x0002) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0002");
+        }
         this.mFilterReadStatus = filterReadStatus;
     }
 
@@ -939,39 +961,44 @@
     }
 
     public void setFilterPriority(int filterPriority) throws IllegalArgumentException {
-        if (filterPriority < 0 || filterPriority > 0x0002)
+        if (filterPriority < 0 || filterPriority > 0x0002) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0002");
+        }
         this.mFilterPriority = filterPriority;
     }
 
     public void setDatabaseIdentifier(long idHigh, long idLow) {
         this.mDatabaseIdentifierHigh = idHigh;
-        this.mDatabaseIdentifierLow  = idLow;
+        this.mDatabaseIdentifierLow = idLow;
     }
 
     public byte[] getDatabaseIdentifier() {
-        if(mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
+        if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
                 && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
             ByteBuffer ret = ByteBuffer.allocate(16);
             ret.putLong(mDatabaseIdentifierHigh);
             ret.putLong(mDatabaseIdentifierLow);
-                return ret.array();
-        }else return null;
+            return ret.array();
+        } else {
+            return null;
+        }
     }
 
     public void setConvoListingVerCounter(long countLow, long countHigh) {
         this.mConvoListingVerCounterHigh = countHigh;
-        this.mConvoListingVerCounterLow  = countLow;
+        this.mConvoListingVerCounterLow = countLow;
     }
 
-    public byte[] getConvoListingVerCounter(){
-        if(mConvoListingVerCounterHigh != INVALID_VALUE_PARAMETER &&
-            mConvoListingVerCounterLow != INVALID_VALUE_PARAMETER) {
+    public byte[] getConvoListingVerCounter() {
+        if (mConvoListingVerCounterHigh != INVALID_VALUE_PARAMETER
+                && mConvoListingVerCounterLow != INVALID_VALUE_PARAMETER) {
             ByteBuffer ret = ByteBuffer.allocate(16);
             ret.putLong(mConvoListingVerCounterHigh);
             ret.putLong(mConvoListingVerCounterLow);
             return ret.array();
-            } else return null;
+        } else {
+            return null;
+        }
     }
 
     public void setFolderVerCounter(long countLow, long countHigh) {
@@ -979,27 +1006,31 @@
         this.mFolderVerCounterLow = countLow;
     }
 
-    public byte[] getFolderVerCounter(){
-        if(mFolderVerCounterHigh != INVALID_VALUE_PARAMETER &&
-                mFolderVerCounterLow != INVALID_VALUE_PARAMETER) {
+    public byte[] getFolderVerCounter() {
+        if (mFolderVerCounterHigh != INVALID_VALUE_PARAMETER
+                && mFolderVerCounterLow != INVALID_VALUE_PARAMETER) {
             ByteBuffer ret = ByteBuffer.allocate(16);
             ret.putLong(mFolderVerCounterHigh);
             ret.putLong(mFolderVerCounterLow);
             return ret.array();
-        } else return null;
+        } else {
+            return null;
+        }
     }
 
-    public SignedLongLong getChatStateConvoId(){
+    public SignedLongLong getChatStateConvoId() {
         return mChatStateConvoId;
     }
 
     public byte[] getChatStateConvoIdByteArray() {
-        if(mChatStateConvoId != null) {
+        if (mChatStateConvoId != null) {
             ByteBuffer ret = ByteBuffer.allocate(16);
             ret.putLong(mChatStateConvoId.getMostSignificantBits());
             ret.putLong(mChatStateConvoId.getLeastSignificantBits());
             return ret.array();
-        } else return null;
+        } else {
+            return null;
+        }
     }
 
     public String getChatStateConvoIdString() {
@@ -1013,20 +1044,20 @@
     }
 
     public void setFilterMsgHandle(String handle) {
-            try {
-                mFilterMsgHandle = BluetoothMapUtils.getLongFromString(handle);
-            } catch (UnsupportedEncodingException e) {
-                Log.w(TAG,"Error creating long from handle string", e);
-            }
+        try {
+            mFilterMsgHandle = BluetoothMapUtils.getLongFromString(handle);
+        } catch (UnsupportedEncodingException e) {
+            Log.w(TAG, "Error creating long from handle string", e);
+        }
     }
 
-    public long getFilterMsgHandle(){
+    public long getFilterMsgHandle() {
         return mFilterMsgHandle;
     }
 
     public String getFilterMsgHandleString() {
         String str = null;
-        if(mFilterMsgHandle != INVALID_VALUE_PARAMETER) {
+        if (mFilterMsgHandle != INVALID_VALUE_PARAMETER) {
             str = BluetoothMapUtils.getLongAsString(mFilterMsgHandle);
         }
         return str;
@@ -1037,8 +1068,9 @@
     }
 
     public void setFilterUidPresent(int present) {
-        if (present < 0 || present > 0x00FF)
+        if (present < 0 || present > 0x00FF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        }
         this.mFilterUidPresent = present;
     }
 
@@ -1047,8 +1079,7 @@
     }
 
 
-
-    public SignedLongLong getFilterConvoId(){
+    public SignedLongLong getFilterConvoId() {
         return mFilterConvoId;
     }
 
@@ -1059,7 +1090,7 @@
      */
     public String getFilterConvoIdString() {
         String str = null;
-        if(mFilterConvoId != null) {
+        if (mFilterConvoId != null) {
             str = BluetoothMapUtils.getLongAsString(mFilterConvoId.getLeastSignificantBits());
         }
         return str;
@@ -1070,14 +1101,15 @@
         try {
             mFilterConvoId = SignedLongLong.fromString(id);
         } catch (UnsupportedEncodingException e) {
-            Log.w(TAG,"Error creating long from id string", e);
+            Log.w(TAG, "Error creating long from id string", e);
         }
     }
 
 
     public void setChatState(int state) {
-        if (state < 0 || state > 0x00FF)
+        if (state < 0 || state > 0x00FF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        }
         this.mChatState = state;
     }
 
@@ -1085,39 +1117,45 @@
         return mChatState;
     }
 
-    public long getLastActivity(){
+    public long getLastActivity() {
         return this.mLastActivity;
     }
-    public String getLastActivityString(){
+
+    public String getLastActivityString() {
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
         Date date = new Date(mLastActivity);
         return format.format(date); // Format to YYYYMMDDTHHMMSS local time
     }
-    public void setLastActivity(long last){
+
+    public void setLastActivity(long last) {
         this.mLastActivity = last;
     }
+
     public void setLastActivity(String lastActivity) throws ParseException {
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmssZ");
         Date date = format.parse(lastActivity);
         this.mLastActivity = date.getTime();
     }
 
-    public void setPresenceStatus(String status){
+    public void setPresenceStatus(String status) {
         this.mPresenceStatus = status;
     }
-    public String getPresenceStatus(){
+
+    public String getPresenceStatus() {
         return this.mPresenceStatus;
     }
 
     public void setFilterPresence(int presence) {
-        if (presence < 0 || presence > 0xFFFF)
+        if (presence < 0 || presence > 0xFFFF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        }
         this.mFilterPresence = presence;
     }
 
     public void setPresenceAvailability(int availability) {
-        if (availability < 0 || availability > 0x00FF)
+        if (availability < 0 || availability > 0x00FF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        }
         this.mPresenceAvailability = availability;
     }
 
@@ -1128,13 +1166,15 @@
     public int getSubjectLength() {
         return mSubjectLength;
     }
+
     public int getAttachment() {
         return mAttachment;
     }
 
     public void setAttachment(int attachment) throws IllegalArgumentException {
-        if (attachment < 0 || attachment > 0x0001)
+        if (attachment < 0 || attachment > 0x0001) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        }
         this.mAttachment = attachment;
     }
 
@@ -1143,8 +1183,9 @@
     }
 
     public void setTransparent(int transparent) throws IllegalArgumentException {
-        if (transparent < 0 || transparent > 0x0001)
+        if (transparent < 0 || transparent > 0x0001) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        }
         this.mTransparent = transparent;
     }
 
@@ -1153,8 +1194,9 @@
     }
 
     public void setRetry(int retry) throws IllegalArgumentException {
-        if (retry < 0 || retry > 0x0001)
+        if (retry < 0 || retry > 0x0001) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        }
         this.mRetry = retry;
     }
 
@@ -1163,8 +1205,9 @@
     }
 
     public void setNewMessage(int newMessage) throws IllegalArgumentException {
-        if (newMessage < 0 || newMessage > 0x0001)
+        if (newMessage < 0 || newMessage > 0x0001) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        }
         this.mNewMessage = newMessage;
     }
 
@@ -1173,8 +1216,9 @@
     }
 
     public void setNotificationStatus(int notificationStatus) throws IllegalArgumentException {
-        if (notificationStatus < 0 || notificationStatus > 0x0001)
+        if (notificationStatus < 0 || notificationStatus > 0x0001) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        }
         this.mNotificationStatus = notificationStatus;
     }
 
@@ -1183,9 +1227,10 @@
     }
 
     public void setNotificationFilter(long notificationFilter) throws IllegalArgumentException {
-        if (notificationFilter < 0 || notificationFilter > 0xFFFFFFFFL)
+        if (notificationFilter < 0 || notificationFilter > 0xFFFFFFFFL) {
             throw new IllegalArgumentException(
                     "Out of range, valid range is 0x0000 to 0xFFFFFFFFL");
+        }
         this.mNotificationFilter = notificationFilter;
     }
 
@@ -1194,8 +1239,9 @@
     }
 
     public void setMasInstanceId(int masInstanceId) {
-        if (masInstanceId < 0 || masInstanceId > 0x00FF)
+        if (masInstanceId < 0 || masInstanceId > 0x00FF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        }
         this.mMasInstanceId = masInstanceId;
     }
 
@@ -1204,18 +1250,20 @@
     }
 
     public void setParameterMask(long parameterMask) {
-        if (parameterMask < 0 || parameterMask > 0xFFFFFFFFL)
+        if (parameterMask < 0 || parameterMask > 0xFFFFFFFFL) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFFFFFF");
+        }
         this.mParameterMask = parameterMask;
     }
 
     public void setConvoParameterMask(long parameterMask) {
-        if (parameterMask < 0 || parameterMask > 0xFFFFFFFFL)
+        if (parameterMask < 0 || parameterMask > 0xFFFFFFFFL) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFFFFFF");
+        }
         this.mConvoParameterMask = parameterMask;
     }
 
-    public long getConvoParameterMask(){
+    public long getConvoParameterMask() {
         return mConvoParameterMask;
     }
 
@@ -1224,8 +1272,9 @@
     }
 
     public void setFolderListingSize(int folderListingSize) {
-        if (folderListingSize < 0 || folderListingSize > 0xFFFF)
+        if (folderListingSize < 0 || folderListingSize > 0xFFFF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        }
         this.mFolderListingSize = folderListingSize;
     }
 
@@ -1234,8 +1283,9 @@
     }
 
     public void setMessageListingSize(int messageListingSize) {
-        if (messageListingSize < 0 || messageListingSize > 0xFFFF)
+        if (messageListingSize < 0 || messageListingSize > 0xFFFF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        }
         this.mMessageListingSize = messageListingSize;
     }
 
@@ -1244,13 +1294,16 @@
     }
 
     public void setConvoListingSize(int convoListingSize) {
-        if (convoListingSize < 0 || convoListingSize > 0xFFFF)
+        if (convoListingSize < 0 || convoListingSize > 0xFFFF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0xFFFF");
+        }
         this.mConvoListingSize = convoListingSize;
     }
+
     public void setSubjectLength(int subjectLength) {
-        if (subjectLength < 0 || subjectLength > 0xFF)
+        if (subjectLength < 0 || subjectLength > 0xFF) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x00FF");
+        }
         this.mSubjectLength = subjectLength;
     }
 
@@ -1259,8 +1312,10 @@
     }
 
     public void setCharset(int charset) {
-        if (charset < 0 || charset > 0x1)
-            throw new IllegalArgumentException("Out of range: " + charset + ", valid range is 0x0000 to 0x0001");
+        if (charset < 0 || charset > 0x1) {
+            throw new IllegalArgumentException(
+                    "Out of range: " + charset + ", valid range is 0x0000 to 0x0001");
+        }
         this.mCharset = charset;
     }
 
@@ -1269,8 +1324,9 @@
     }
 
     public void setFractionRequest(int fractionRequest) {
-        if (fractionRequest < 0 || fractionRequest > 0x1)
+        if (fractionRequest < 0 || fractionRequest > 0x1) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        }
         this.mFractionRequest = fractionRequest;
     }
 
@@ -1279,8 +1335,9 @@
     }
 
     public void setFractionDeliver(int fractionDeliver) {
-        if (fractionDeliver < 0 || fractionDeliver > 0x1)
+        if (fractionDeliver < 0 || fractionDeliver > 0x1) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        }
         this.mFractionDeliver = fractionDeliver;
     }
 
@@ -1289,8 +1346,9 @@
     }
 
     public void setStatusIndicator(int statusIndicator) {
-        if (statusIndicator < 0 || statusIndicator > 0x1)
+        if (statusIndicator < 0 || statusIndicator > 0x1) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        }
         this.mStatusIndicator = statusIndicator;
     }
 
@@ -1299,8 +1357,9 @@
     }
 
     public void setStatusValue(int statusValue) {
-        if (statusValue < 0 || statusValue > 0x1)
+        if (statusValue < 0 || statusValue > 0x1) {
             throw new IllegalArgumentException("Out of range, valid range is 0x0000 to 0x0001");
+        }
         this.mStatusValue = statusValue;
     }
 
@@ -1325,6 +1384,4 @@
     }
 
 
-
-
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index c5f8384..de1c59d 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -25,23 +25,25 @@
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.PhoneLookup;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-import android.provider.Telephony.MmsSms;
 import android.provider.Telephony.CanonicalAddressesColumns;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
 import android.provider.Telephony.Threads;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
-import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.bluetooth.DeviceWorkArounds;
 import com.android.bluetooth.SignedLongLong;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
 import com.android.bluetooth.mapapi.BluetoothMapContract.ConversationColumns;
+
 import com.google.android.mms.pdu.CharacterSets;
 import com.google.android.mms.pdu.PduHeaders;
 
@@ -66,47 +68,47 @@
     private static final boolean V = BluetoothMapService.VERBOSE;
 
     // Parameter Mask for selection of parameters to return in listings
-    private static final int MASK_SUBJECT               = 0x00000001;
-    private static final int MASK_DATETIME              = 0x00000002;
-    private static final int MASK_SENDER_NAME           = 0x00000004;
-    private static final int MASK_SENDER_ADDRESSING     = 0x00000008;
-    private static final int MASK_RECIPIENT_NAME        = 0x00000010;
-    private static final int MASK_RECIPIENT_ADDRESSING  = 0x00000020;
-    private static final int MASK_TYPE                  = 0x00000040;
-    private static final int MASK_SIZE                  = 0x00000080;
-    private static final int MASK_RECEPTION_STATUS      = 0x00000100;
-    private static final int MASK_TEXT                  = 0x00000200;
-    private static final int MASK_ATTACHMENT_SIZE       = 0x00000400;
-    private static final int MASK_PRIORITY              = 0x00000800;
-    private static final int MASK_READ                  = 0x00001000;
-    private static final int MASK_SENT                  = 0x00002000;
-    private static final int MASK_PROTECTED             = 0x00004000;
-    private static final int MASK_REPLYTO_ADDRESSING    = 0x00008000;
+    private static final int MASK_SUBJECT = 0x00000001;
+    private static final int MASK_DATETIME = 0x00000002;
+    private static final int MASK_SENDER_NAME = 0x00000004;
+    private static final int MASK_SENDER_ADDRESSING = 0x00000008;
+    private static final int MASK_RECIPIENT_NAME = 0x00000010;
+    private static final int MASK_RECIPIENT_ADDRESSING = 0x00000020;
+    private static final int MASK_TYPE = 0x00000040;
+    private static final int MASK_SIZE = 0x00000080;
+    private static final int MASK_RECEPTION_STATUS = 0x00000100;
+    private static final int MASK_TEXT = 0x00000200;
+    private static final int MASK_ATTACHMENT_SIZE = 0x00000400;
+    private static final int MASK_PRIORITY = 0x00000800;
+    private static final int MASK_READ = 0x00001000;
+    private static final int MASK_SENT = 0x00002000;
+    private static final int MASK_PROTECTED = 0x00004000;
+    private static final int MASK_REPLYTO_ADDRESSING = 0x00008000;
     // TODO: Duplicate in proposed spec
     // private static final int MASK_RECEPTION_STATE       = 0x00010000;
-    private static final int MASK_DELIVERY_STATUS       = 0x00020000;
-    private static final int MASK_CONVERSATION_ID       = 0x00040000;
-    private static final int MASK_CONVERSATION_NAME     = 0x00080000;
-    private static final int MASK_FOLDER_TYPE           = 0x00100000;
+    private static final int MASK_DELIVERY_STATUS = 0x00020000;
+    private static final int MASK_CONVERSATION_ID = 0x00040000;
+    private static final int MASK_CONVERSATION_NAME = 0x00080000;
+    private static final int MASK_FOLDER_TYPE = 0x00100000;
     // TODO: about to be removed from proposed spec
     // private static final int MASK_SEQUENCE_NUMBER       = 0x00200000;
-    private static final int MASK_ATTACHMENT_MIME       = 0x00400000;
+    private static final int MASK_ATTACHMENT_MIME = 0x00400000;
 
-    private static final int  CONVO_PARAM_MASK_CONVO_NAME              = 0x00000001;
-    private static final int  CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY     = 0x00000002;
-    private static final int  CONVO_PARAM_MASK_CONVO_READ_STATUS       = 0x00000004;
-    private static final int  CONVO_PARAM_MASK_CONVO_VERSION_COUNTER   = 0x00000008;
-    private static final int  CONVO_PARAM_MASK_CONVO_SUMMARY           = 0x00000010;
-    private static final int  CONVO_PARAM_MASK_PARTTICIPANTS           = 0x00000020;
-    private static final int  CONVO_PARAM_MASK_PART_UCI                = 0x00000040;
-    private static final int  CONVO_PARAM_MASK_PART_DISP_NAME          = 0x00000080;
-    private static final int  CONVO_PARAM_MASK_PART_CHAT_STATE         = 0x00000100;
-    private static final int  CONVO_PARAM_MASK_PART_LAST_ACTIVITY      = 0x00000200;
-    private static final int  CONVO_PARAM_MASK_PART_X_BT_UID           = 0x00000400;
-    private static final int  CONVO_PARAM_MASK_PART_NAME               = 0x00000800;
-    private static final int  CONVO_PARAM_MASK_PART_PRESENCE           = 0x00001000;
-    private static final int  CONVO_PARAM_MASK_PART_PRESENCE_TEXT      = 0x00002000;
-    private static final int  CONVO_PARAM_MASK_PART_PRIORITY           = 0x00004000;
+    private static final int CONVO_PARAM_MASK_CONVO_NAME = 0x00000001;
+    private static final int CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY = 0x00000002;
+    private static final int CONVO_PARAM_MASK_CONVO_READ_STATUS = 0x00000004;
+    private static final int CONVO_PARAM_MASK_CONVO_VERSION_COUNTER = 0x00000008;
+    private static final int CONVO_PARAM_MASK_CONVO_SUMMARY = 0x00000010;
+    private static final int CONVO_PARAM_MASK_PARTTICIPANTS = 0x00000020;
+    private static final int CONVO_PARAM_MASK_PART_UCI = 0x00000040;
+    private static final int CONVO_PARAM_MASK_PART_DISP_NAME = 0x00000080;
+    private static final int CONVO_PARAM_MASK_PART_CHAT_STATE = 0x00000100;
+    private static final int CONVO_PARAM_MASK_PART_LAST_ACTIVITY = 0x00000200;
+    private static final int CONVO_PARAM_MASK_PART_X_BT_UID = 0x00000400;
+    private static final int CONVO_PARAM_MASK_PART_NAME = 0x00000800;
+    private static final int CONVO_PARAM_MASK_PART_PRESENCE = 0x00001000;
+    private static final int CONVO_PARAM_MASK_PART_PRESENCE_TEXT = 0x00002000;
+    private static final int CONVO_PARAM_MASK_PART_PRIORITY = 0x00004000;
 
     /* Default values for omitted or 0 parameterMask application parameters */
     // MAP specification states that the default value for parameter mask are
@@ -114,33 +116,29 @@
     public static final long PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL;
     public static final long CONVO_PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL;
     public static final long CONVO_PARAMETER_MASK_DEFAULT =
-            CONVO_PARAM_MASK_CONVO_NAME |
-            CONVO_PARAM_MASK_PARTTICIPANTS |
-            CONVO_PARAM_MASK_PART_UCI |
-            CONVO_PARAM_MASK_PART_DISP_NAME;
-
-
+            CONVO_PARAM_MASK_CONVO_NAME | CONVO_PARAM_MASK_PARTTICIPANTS | CONVO_PARAM_MASK_PART_UCI
+                    | CONVO_PARAM_MASK_PART_DISP_NAME;
 
 
     private static final int FILTER_READ_STATUS_UNREAD_ONLY = 0x01;
-    private static final int FILTER_READ_STATUS_READ_ONLY   = 0x02;
-    private static final int FILTER_READ_STATUS_ALL         = 0x00;
+    private static final int FILTER_READ_STATUS_READ_ONLY = 0x02;
+    private static final int FILTER_READ_STATUS_ALL = 0x00;
 
     /* Type of MMS address. From Telephony.java it must be one of PduHeaders.BCC, */
     /* PduHeaders.CC, PduHeaders.FROM, PduHeaders.TO. These are from PduHeaders.java */
-    public static final int MMS_FROM    = 0x89;
-    public static final int MMS_TO      = 0x97;
-    public static final int MMS_BCC     = 0x81;
-    public static final int MMS_CC      = 0x82;
+    public static final int MMS_FROM = 0x89;
+    public static final int MMS_TO = 0x97;
+    public static final int MMS_BCC = 0x81;
+    public static final int MMS_CC = 0x82;
 
     /* OMA-TS-MMS-ENC defined many types in X-Mms-Message-Type.
        Only m-send-req (128) m-retrieve-conf (132), m-notification-ind (130)
        are interested by user */
-    private static final String INTERESTED_MESSAGE_TYPE_CLAUSE = String
-            .format("( %s = %d OR %s = %d OR %s = %d )", Mms.MESSAGE_TYPE,
-            PduHeaders.MESSAGE_TYPE_SEND_REQ, Mms.MESSAGE_TYPE,
-            PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF, Mms.MESSAGE_TYPE,
-            PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND );
+    private static final String INTERESTED_MESSAGE_TYPE_CLAUSE =
+            String.format("( %s = %d OR %s = %d OR %s = %d )", Mms.MESSAGE_TYPE,
+                    PduHeaders.MESSAGE_TYPE_SEND_REQ, Mms.MESSAGE_TYPE,
+                    PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF, Mms.MESSAGE_TYPE,
+                    PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
 
     public static final String INSERT_ADDRES_TOKEN = "insert-address-token";
 
@@ -155,81 +153,81 @@
     private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
     private int mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10;
 
-    static final String[] SMS_PROJECTION = new String[] {
-        BaseColumns._ID,
-        Sms.THREAD_ID,
-        Sms.ADDRESS,
-        Sms.BODY,
-        Sms.DATE,
-        Sms.READ,
-        Sms.TYPE,
-        Sms.STATUS,
-        Sms.LOCKED,
-        Sms.ERROR_CODE
+    static final String[] SMS_PROJECTION = new String[]{
+            BaseColumns._ID,
+            Sms.THREAD_ID,
+            Sms.ADDRESS,
+            Sms.BODY,
+            Sms.DATE,
+            Sms.READ,
+            Sms.TYPE,
+            Sms.STATUS,
+            Sms.LOCKED,
+            Sms.ERROR_CODE
     };
 
-    static final String[] MMS_PROJECTION = new String[] {
-        BaseColumns._ID,
-        Mms.THREAD_ID,
-        Mms.MESSAGE_ID,
-        Mms.MESSAGE_SIZE,
-        Mms.SUBJECT,
-        Mms.CONTENT_TYPE,
-        Mms.TEXT_ONLY,
-        Mms.DATE,
-        Mms.DATE_SENT,
-        Mms.READ,
-        Mms.MESSAGE_BOX,
-        Mms.STATUS,
-        Mms.PRIORITY,
+    static final String[] MMS_PROJECTION = new String[]{
+            BaseColumns._ID,
+            Mms.THREAD_ID,
+            Mms.MESSAGE_ID,
+            Mms.MESSAGE_SIZE,
+            Mms.SUBJECT,
+            Mms.CONTENT_TYPE,
+            Mms.TEXT_ONLY,
+            Mms.DATE,
+            Mms.DATE_SENT,
+            Mms.READ,
+            Mms.MESSAGE_BOX,
+            Mms.STATUS,
+            Mms.PRIORITY,
     };
 
-    static final String[] SMS_CONVO_PROJECTION = new String[] {
-        BaseColumns._ID,
-        Sms.THREAD_ID,
-        Sms.ADDRESS,
-        Sms.DATE,
-        Sms.READ,
-        Sms.TYPE,
-        Sms.STATUS,
-        Sms.LOCKED,
-        Sms.ERROR_CODE
+    static final String[] SMS_CONVO_PROJECTION = new String[]{
+            BaseColumns._ID,
+            Sms.THREAD_ID,
+            Sms.ADDRESS,
+            Sms.DATE,
+            Sms.READ,
+            Sms.TYPE,
+            Sms.STATUS,
+            Sms.LOCKED,
+            Sms.ERROR_CODE
     };
 
-    static final String[] MMS_CONVO_PROJECTION = new String[] {
-        BaseColumns._ID,
-        Mms.THREAD_ID,
-        Mms.MESSAGE_ID,
-        Mms.MESSAGE_SIZE,
-        Mms.SUBJECT,
-        Mms.CONTENT_TYPE,
-        Mms.TEXT_ONLY,
-        Mms.DATE,
-        Mms.DATE_SENT,
-        Mms.READ,
-        Mms.MESSAGE_BOX,
-        Mms.STATUS,
-        Mms.PRIORITY,
-        Mms.Addr.ADDRESS
+    static final String[] MMS_CONVO_PROJECTION = new String[]{
+            BaseColumns._ID,
+            Mms.THREAD_ID,
+            Mms.MESSAGE_ID,
+            Mms.MESSAGE_SIZE,
+            Mms.SUBJECT,
+            Mms.CONTENT_TYPE,
+            Mms.TEXT_ONLY,
+            Mms.DATE,
+            Mms.DATE_SENT,
+            Mms.READ,
+            Mms.MESSAGE_BOX,
+            Mms.STATUS,
+            Mms.PRIORITY,
+            Mms.Addr.ADDRESS
     };
 
     /* CONVO LISTING projections and column indexes */
     private static final String[] MMS_SMS_THREAD_PROJECTION = {
-        Threads._ID,
-        Threads.DATE,
-        Threads.SNIPPET,
-        Threads.SNIPPET_CHARSET,
-        Threads.READ,
-        Threads.RECIPIENT_IDS
+            Threads._ID,
+            Threads.DATE,
+            Threads.SNIPPET,
+            Threads.SNIPPET_CHARSET,
+            Threads.READ,
+            Threads.RECIPIENT_IDS
     };
 
-    private static final String[] CONVO_VERSION_PROJECTION = new String[] {
+    private static final String[] CONVO_VERSION_PROJECTION = new String[]{
         /* Thread information */
-        ConversationColumns.THREAD_ID,
-        ConversationColumns.THREAD_NAME,
-        ConversationColumns.READ_STATUS,
-        ConversationColumns.LAST_THREAD_ACTIVITY,
-        ConversationColumns.SUMMARY,
+            ConversationColumns.THREAD_ID,
+            ConversationColumns.THREAD_NAME,
+            ConversationColumns.READ_STATUS,
+            ConversationColumns.LAST_THREAD_ACTIVITY,
+            ConversationColumns.SUMMARY,
     };
 
     /* Optimize the Cursor access to avoid the need to do a getColumnIndex() */
@@ -239,6 +237,7 @@
     private static final int MMS_SMS_THREAD_COL_SNIPPET_CS;
     private static final int MMS_SMS_THREAD_COL_READ;
     private static final int MMS_SMS_THREAD_COL_RECIPIENT_IDS;
+
     static {
         // TODO: This might not work, if the projection is mapped in the content provider...
         //       Change to init at first query? (Current use in the AOSP code is hard coded values
@@ -253,10 +252,10 @@
     }
 
     private class FilterInfo {
-        public static final int TYPE_SMS    = 0;
-        public static final int TYPE_MMS    = 1;
-        public static final int TYPE_EMAIL  = 2;
-        public static final int TYPE_IM     = 3;
+        public static final int TYPE_SMS = 0;
+        public static final int TYPE_MMS = 1;
+        public static final int TYPE_EMAIL = 2;
+        public static final int TYPE_IM = 3;
 
         // TODO: Change to ENUM, to ensure correct usage
         int mMsgType = TYPE_SMS;
@@ -264,177 +263,160 @@
         String mPhoneNum = null;
         String mPhoneAlphaTag = null;
         /*column indices used to optimize queries */
-        public int mMessageColId                = -1;
-        public int mMessageColDate              = -1;
-        public int mMessageColBody              = -1;
-        public int mMessageColSubject           = -1;
-        public int mMessageColFolder            = -1;
-        public int mMessageColRead              = -1;
-        public int mMessageColSize              = -1;
-        public int mMessageColFromAddress       = -1;
-        public int mMessageColToAddress         = -1;
-        public int mMessageColCcAddress         = -1;
-        public int mMessageColBccAddress        = -1;
-        public int mMessageColReplyTo           = -1;
-        public int mMessageColAccountId         = -1;
-        public int mMessageColAttachment        = -1;
-        public int mMessageColAttachmentSize    = -1;
-        public int mMessageColAttachmentMime    = -1;
-        public int mMessageColPriority          = -1;
-        public int mMessageColProtected         = -1;
-        public int mMessageColReception         = -1;
-        public int mMessageColDelivery          = -1;
-        public int mMessageColThreadId          = -1;
-        public int mMessageColThreadName        = -1;
+        public int mMessageColId = -1;
+        public int mMessageColDate = -1;
+        public int mMessageColBody = -1;
+        public int mMessageColSubject = -1;
+        public int mMessageColFolder = -1;
+        public int mMessageColRead = -1;
+        public int mMessageColSize = -1;
+        public int mMessageColFromAddress = -1;
+        public int mMessageColToAddress = -1;
+        public int mMessageColCcAddress = -1;
+        public int mMessageColBccAddress = -1;
+        public int mMessageColReplyTo = -1;
+        public int mMessageColAccountId = -1;
+        public int mMessageColAttachment = -1;
+        public int mMessageColAttachmentSize = -1;
+        public int mMessageColAttachmentMime = -1;
+        public int mMessageColPriority = -1;
+        public int mMessageColProtected = -1;
+        public int mMessageColReception = -1;
+        public int mMessageColDelivery = -1;
+        public int mMessageColThreadId = -1;
+        public int mMessageColThreadName = -1;
 
-        public int mSmsColFolder            = -1;
-        public int mSmsColRead              = -1;
-        public int mSmsColId                = -1;
-        public int mSmsColSubject           = -1;
-        public int mSmsColAddress           = -1;
-        public int mSmsColDate              = -1;
-        public int mSmsColType              = -1;
-        public int mSmsColThreadId          = -1;
+        public int mSmsColFolder = -1;
+        public int mSmsColRead = -1;
+        public int mSmsColId = -1;
+        public int mSmsColSubject = -1;
+        public int mSmsColAddress = -1;
+        public int mSmsColDate = -1;
+        public int mSmsColType = -1;
+        public int mSmsColThreadId = -1;
 
-        public int mMmsColRead              = -1;
-        public int mMmsColFolder            = -1;
-        public int mMmsColAttachmentSize    = -1;
-        public int mMmsColTextOnly          = -1;
-        public int mMmsColId                = -1;
-        public int mMmsColSize              = -1;
-        public int mMmsColDate              = -1;
-        public int mMmsColSubject           = -1;
-        public int mMmsColThreadId          = -1;
+        public int mMmsColRead = -1;
+        public int mMmsColFolder = -1;
+        public int mMmsColAttachmentSize = -1;
+        public int mMmsColTextOnly = -1;
+        public int mMmsColId = -1;
+        public int mMmsColSize = -1;
+        public int mMmsColDate = -1;
+        public int mMmsColSubject = -1;
+        public int mMmsColThreadId = -1;
 
-        public int mConvoColConvoId         = -1;
-        public int mConvoColLastActivity    = -1;
-        public int mConvoColName            = -1;
-        public int mConvoColRead            = -1;
-        public int mConvoColVersionCounter  = -1;
-        public int mConvoColSummary         = -1;
-        public int mContactColBtUid         = -1;
-        public int mContactColChatState     = -1;
-        public int mContactColContactUci    = -1;
-        public int mContactColNickname      = -1;
-        public int mContactColLastActive    = -1;
-        public int mContactColName          = -1;
+        public int mConvoColConvoId = -1;
+        public int mConvoColLastActivity = -1;
+        public int mConvoColName = -1;
+        public int mConvoColRead = -1;
+        public int mConvoColVersionCounter = -1;
+        public int mConvoColSummary = -1;
+        public int mContactColBtUid = -1;
+        public int mContactColChatState = -1;
+        public int mContactColContactUci = -1;
+        public int mContactColNickname = -1;
+        public int mContactColLastActive = -1;
+        public int mContactColName = -1;
         public int mContactColPresenceState = -1;
-        public int mContactColPresenceText  = -1;
-        public int mContactColPriority      = -1;
+        public int mContactColPresenceText = -1;
+        public int mContactColPriority = -1;
 
 
         public void setMessageColumns(Cursor c) {
-            mMessageColId               = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns._ID);
-            mMessageColDate             = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.DATE);
-            mMessageColSubject          = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.SUBJECT);
-            mMessageColFolder           = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.FOLDER_ID);
-            mMessageColRead             = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.FLAG_READ);
-            mMessageColSize             = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.MESSAGE_SIZE);
-            mMessageColFromAddress      = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.FROM_LIST);
-            mMessageColToAddress        = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.TO_LIST);
-            mMessageColAttachment       = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT);
-            mMessageColAttachmentSize   = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE);
-            mMessageColPriority         = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY);
-            mMessageColProtected        = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.FLAG_PROTECTED);
-            mMessageColReception        = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.RECEPTION_STATE);
-            mMessageColDelivery         = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.DEVILERY_STATE);
-            mMessageColThreadId         = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.THREAD_ID);
+            mMessageColId = c.getColumnIndex(BluetoothMapContract.MessageColumns._ID);
+            mMessageColDate = c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE);
+            mMessageColSubject = c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT);
+            mMessageColFolder = c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID);
+            mMessageColRead = c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ);
+            mMessageColSize = c.getColumnIndex(BluetoothMapContract.MessageColumns.MESSAGE_SIZE);
+            mMessageColFromAddress =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST);
+            mMessageColToAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST);
+            mMessageColAttachment =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT);
+            mMessageColAttachmentSize =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE);
+            mMessageColPriority =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY);
+            mMessageColProtected =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_PROTECTED);
+            mMessageColReception =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.RECEPTION_STATE);
+            mMessageColDelivery =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.DEVILERY_STATE);
+            mMessageColThreadId = c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID);
         }
 
         public void setEmailMessageColumns(Cursor c) {
             setMessageColumns(c);
-            mMessageColCcAddress        = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.CC_LIST);
-            mMessageColBccAddress       = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.BCC_LIST);
-            mMessageColReplyTo          = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.REPLY_TO_LIST);
+            mMessageColCcAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.CC_LIST);
+            mMessageColBccAddress = c.getColumnIndex(BluetoothMapContract.MessageColumns.BCC_LIST);
+            mMessageColReplyTo =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.REPLY_TO_LIST);
         }
 
         public void setImMessageColumns(Cursor c) {
             setMessageColumns(c);
-            mMessageColThreadName       = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.THREAD_NAME);
-            mMessageColAttachmentMime   = c.getColumnIndex(
-                    BluetoothMapContract.MessageColumns.ATTACHMENT_MINE_TYPES);
+            mMessageColThreadName =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_NAME);
+            mMessageColAttachmentMime =
+                    c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_MINE_TYPES);
             //TODO this is temporary as text should come from parts table instead
             mMessageColBody = c.getColumnIndex(BluetoothMapContract.MessageColumns.BODY);
 
         }
 
         public void setEmailImConvoColumns(Cursor c) {
-            mConvoColConvoId            = c.getColumnIndex(
-                    BluetoothMapContract.ConversationColumns.THREAD_ID);
-            mConvoColLastActivity       = c.getColumnIndex(
-                    BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY);
-            mConvoColName               = c.getColumnIndex(
-                    BluetoothMapContract.ConversationColumns.THREAD_NAME);
-            mConvoColRead               = c.getColumnIndex(
-                    BluetoothMapContract.ConversationColumns.READ_STATUS);
-            mConvoColVersionCounter     = c.getColumnIndex(
-                    BluetoothMapContract.ConversationColumns.VERSION_COUNTER);
-            mConvoColSummary            = c.getColumnIndex(
-                    BluetoothMapContract.ConversationColumns.SUMMARY);
+            mConvoColConvoId = c.getColumnIndex(BluetoothMapContract.ConversationColumns.THREAD_ID);
+            mConvoColLastActivity =
+                    c.getColumnIndex(BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY);
+            mConvoColName = c.getColumnIndex(BluetoothMapContract.ConversationColumns.THREAD_NAME);
+            mConvoColRead = c.getColumnIndex(BluetoothMapContract.ConversationColumns.READ_STATUS);
+            mConvoColVersionCounter =
+                    c.getColumnIndex(BluetoothMapContract.ConversationColumns.VERSION_COUNTER);
+            mConvoColSummary = c.getColumnIndex(BluetoothMapContract.ConversationColumns.SUMMARY);
             setEmailImConvoContactColumns(c);
         }
 
-        public void setEmailImConvoContactColumns(Cursor c){
-            mContactColBtUid         = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.X_BT_UID);
-            mContactColChatState     = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
-            mContactColContactUci     = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.UCI);
-            mContactColNickname      = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.NICKNAME);
-            mContactColLastActive    = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
-            mContactColName          = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.NAME);
-            mContactColPresenceState = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
-            mContactColPresenceText = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
-            mContactColPriority      = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.PRIORITY);
+        public void setEmailImConvoContactColumns(Cursor c) {
+            mContactColBtUid = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.X_BT_UID);
+            mContactColChatState =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
+            mContactColContactUci = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.UCI);
+            mContactColNickname =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NICKNAME);
+            mContactColLastActive =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
+            mContactColName = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME);
+            mContactColPresenceState =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
+            mContactColPresenceText =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
+            mContactColPriority =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.PRIORITY);
         }
 
         public void setSmsColumns(Cursor c) {
-            mSmsColId      = c.getColumnIndex(BaseColumns._ID);
-            mSmsColFolder  = c.getColumnIndex(Sms.TYPE);
-            mSmsColRead    = c.getColumnIndex(Sms.READ);
+            mSmsColId = c.getColumnIndex(BaseColumns._ID);
+            mSmsColFolder = c.getColumnIndex(Sms.TYPE);
+            mSmsColRead = c.getColumnIndex(Sms.READ);
             mSmsColSubject = c.getColumnIndex(Sms.BODY);
             mSmsColAddress = c.getColumnIndex(Sms.ADDRESS);
-            mSmsColDate    = c.getColumnIndex(Sms.DATE);
-            mSmsColType    = c.getColumnIndex(Sms.TYPE);
-            mSmsColThreadId= c.getColumnIndex(Sms.THREAD_ID);
+            mSmsColDate = c.getColumnIndex(Sms.DATE);
+            mSmsColType = c.getColumnIndex(Sms.TYPE);
+            mSmsColThreadId = c.getColumnIndex(Sms.THREAD_ID);
         }
 
         public void setMmsColumns(Cursor c) {
-            mMmsColId              = c.getColumnIndex(BaseColumns._ID);
-            mMmsColFolder          = c.getColumnIndex(Mms.MESSAGE_BOX);
-            mMmsColRead            = c.getColumnIndex(Mms.READ);
-            mMmsColAttachmentSize  = c.getColumnIndex(Mms.MESSAGE_SIZE);
-            mMmsColTextOnly        = c.getColumnIndex(Mms.TEXT_ONLY);
-            mMmsColSize            = c.getColumnIndex(Mms.MESSAGE_SIZE);
-            mMmsColDate            = c.getColumnIndex(Mms.DATE);
-            mMmsColSubject         = c.getColumnIndex(Mms.SUBJECT);
-            mMmsColThreadId        = c.getColumnIndex(Mms.THREAD_ID);
+            mMmsColId = c.getColumnIndex(BaseColumns._ID);
+            mMmsColFolder = c.getColumnIndex(Mms.MESSAGE_BOX);
+            mMmsColRead = c.getColumnIndex(Mms.READ);
+            mMmsColAttachmentSize = c.getColumnIndex(Mms.MESSAGE_SIZE);
+            mMmsColTextOnly = c.getColumnIndex(Mms.TEXT_ONLY);
+            mMmsColSize = c.getColumnIndex(Mms.MESSAGE_SIZE);
+            mMmsColDate = c.getColumnIndex(Mms.DATE);
+            mMmsColSubject = c.getColumnIndex(Mms.SUBJECT);
+            mMmsColThreadId = c.getColumnIndex(Mms.THREAD_ID);
         }
     }
 
@@ -444,10 +426,12 @@
         mResolver = mContext.getContentResolver();
         mMasInstance = mas;
         if (mResolver == null) {
-            if (D) Log.d(TAG, "getContentResolver failed");
+            if (D) {
+                Log.d(TAG, "getContentResolver failed");
+            }
         }
 
-        if(account != null){
+        if (account != null) {
             mBaseUri = account.mBase_uri + "/";
             mAccount = account;
         } else {
@@ -455,30 +439,35 @@
             mAccount = null;
         }
     }
+
     private static void close(Closeable c) {
         try {
-          if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         } catch (IOException e) {
         }
     }
-    private void setProtected(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+
+    private void setProtected(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_PROTECTED) != 0) {
             String protect = "no";
-            if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                fi.mMsgType == FilterInfo.TYPE_IM) {
+            if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 int flagProtected = c.getInt(fi.mMessageColProtected);
                 if (flagProtected == 1) {
                     protect = "yes";
                 }
             }
-            if (V) Log.d(TAG, "setProtected: " + protect + "\n");
+            if (V) {
+                Log.d(TAG, "setProtected: " + protect + "\n");
+            }
             e.setProtect(protect);
         }
     }
 
-    private void setThreadId(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setThreadId(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_CONVERSATION_ID) != 0) {
             long threadId = 0;
             TYPE type = TYPE.SMS_GSM; // Just used for handle encoding
@@ -486,40 +475,42 @@
                 threadId = c.getLong(fi.mSmsColThreadId);
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 threadId = c.getLong(fi.mMmsColThreadId);
-                type = TYPE.MMS;// Just used for handle encoding
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                type = TYPE.MMS; // Just used for handle encoding
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 threadId = c.getLong(fi.mMessageColThreadId);
-                type = TYPE.EMAIL;// Just used for handle encoding
+                type = TYPE.EMAIL; // Just used for handle encoding
             }
-            e.setThreadId(threadId,type);
-            if (V) Log.d(TAG, "setThreadId: " + threadId + "\n");
+            e.setThreadId(threadId, type);
+            if (V) {
+                Log.d(TAG, "setThreadId: " + threadId + "\n");
+            }
         }
     }
 
-    private void setThreadName(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setThreadName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         // TODO: Maybe this should be valid for SMS/MMS
         if ((ap.getParameterMask() & MASK_CONVERSATION_NAME) != 0) {
             if (fi.mMsgType == FilterInfo.TYPE_IM) {
                 String threadName = c.getString(fi.mMessageColThreadName);
                 e.setThreadName(threadName);
-                if (V) Log.d(TAG, "setThreadName: " + threadName + "\n");
+                if (V) {
+                    Log.d(TAG, "setThreadName: " + threadName + "\n");
+                }
             }
         }
     }
 
 
-    private void setSent(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setSent(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_SENT) != 0) {
             int msgType = 0;
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 msgType = c.getInt(fi.mSmsColFolder);
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 msgType = c.getInt(fi.mMmsColFolder);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 msgType = c.getInt(fi.mMessageColFolder);
             }
             String sent = null;
@@ -528,44 +519,49 @@
             } else {
                 sent = "no";
             }
-            if (V) Log.d(TAG, "setSent: " + sent);
+            if (V) {
+                Log.d(TAG, "setSent: " + sent);
+            }
             e.setSent(sent);
         }
     }
 
-    private void setRead(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setRead(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         int read = 0;
         if (fi.mMsgType == FilterInfo.TYPE_SMS) {
             read = c.getInt(fi.mSmsColRead);
         } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
             read = c.getInt(fi.mMmsColRead);
-        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                   fi.mMsgType == FilterInfo.TYPE_IM) {
+        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
             read = c.getInt(fi.mMessageColRead);
         }
         String setread = null;
 
-        if (V) Log.d(TAG, "setRead: " + setread);
-        e.setRead((read==1?true:false), ((ap.getParameterMask() & MASK_READ) != 0));
+        if (V) {
+            Log.d(TAG, "setRead: " + setread);
+        }
+        e.setRead((read == 1), ((ap.getParameterMask() & MASK_READ) != 0));
     }
-    private void setConvoRead(BluetoothMapConvoListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+
+    private void setConvoRead(BluetoothMapConvoListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         String setread = null;
         int read = 0;
-            read = c.getInt(fi.mConvoColRead);
+        read = c.getInt(fi.mConvoColRead);
 
 
-        if (V) Log.d(TAG, "setRead: " + setread);
-        e.setRead((read==1?true:false), ((ap.getParameterMask() & MASK_READ) != 0));
+        if (V) {
+            Log.d(TAG, "setRead: " + setread);
+        }
+        e.setRead((read == 1), ((ap.getParameterMask() & MASK_READ) != 0));
     }
 
-    private void setPriority(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setPriority(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_PRIORITY) != 0) {
             String priority = "no";
-            if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                fi.mMsgType == FilterInfo.TYPE_IM) {
+            if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 int highPriority = c.getInt(fi.mMessageColPriority);
                 if (highPriority == 1) {
                     priority = "yes";
@@ -578,7 +574,9 @@
             if (pri == PduHeaders.PRIORITY_HIGH) {
                 priority = "yes";
             }
-            if (V) Log.d(TAG, "setPriority: " + priority);
+            if (V) {
+                Log.d(TAG, "setPriority: " + priority);
+            }
             e.setPriority(priority);
         }
     }
@@ -590,20 +588,22 @@
      * the total message size. To provide a more accurate attachment size, one could
      * extract the length (in bytes) of the text parts.
      */
-    private void setAttachment(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setAttachment(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_ATTACHMENT_SIZE) != 0) {
             int size = 0;
             String attachmentMimeTypes = null;
             if (fi.mMsgType == FilterInfo.TYPE_MMS) {
-                if(c.getInt(fi.mMmsColTextOnly) == 0) {
+                if (c.getInt(fi.mMmsColTextOnly) == 0) {
                     size = c.getInt(fi.mMmsColAttachmentSize);
-                    if(size <= 0) {
+                    if (size <= 0) {
                         // We know there are attachments, since it is not TextOnly
                         // Hence the size in the database must be wrong.
                         // Set size to 1 to indicate to the client, that attachments are present
-                        if (D) Log.d(TAG, "Error in message database, size reported as: " + size
-                                + " Changing size to 1");
+                        if (D) {
+                            Log.d(TAG, "Error in message database, size reported as: " + size
+                                    + " Changing size to 1");
+                        }
                         size = 1;
                     }
                     // TODO: Add handling of attachemnt mime types
@@ -611,9 +611,11 @@
             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
                 int attachment = c.getInt(fi.mMessageColAttachment);
                 size = c.getInt(fi.mMessageColAttachmentSize);
-                if(attachment == 1 && size == 0) {
-                    if (D) Log.d(TAG, "Error in message database, attachment size reported as: " + size
-                            + " Changing size to 1");
+                if (attachment == 1 && size == 0) {
+                    if (D) {
+                        Log.d(TAG, "Error in message database, attachment size reported as: " + size
+                                + " Changing size to 1");
+                    }
                     size = 1; /* Ensure we indicate we have attachments in the size, if the
                                  message has attachments, in case the e-mail client do not
                                  report a size */
@@ -621,26 +623,28 @@
             } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
                 int attachment = c.getInt(fi.mMessageColAttachment);
                 size = c.getInt(fi.mMessageColAttachmentSize);
-                if(attachment == 1 && size == 0) {
+                if (attachment == 1 && size == 0) {
                     size = 1; /* Ensure we indicate we have attachments in the size, it the
                                   message has attachments, in case the e-mail client do not
                                   report a size */
-                    attachmentMimeTypes =  c.getString(fi.mMessageColAttachmentMime);
+                    attachmentMimeTypes = c.getString(fi.mMessageColAttachmentMime);
                 }
             }
-            if (V) Log.d(TAG, "setAttachmentSize: " + size + "\n" +
-                              "setAttachmentMimeTypes: " + attachmentMimeTypes );
+            if (V) {
+                Log.d(TAG, "setAttachmentSize: " + size + "\n" + "setAttachmentMimeTypes: "
+                        + attachmentMimeTypes);
+            }
             e.setAttachmentSize(size);
 
-            if( (mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10)
-                    && ((ap.getParameterMask() & MASK_ATTACHMENT_MIME) != 0) ){
+            if ((mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10) && (
+                    (ap.getParameterMask() & MASK_ATTACHMENT_MIME) != 0)) {
                 e.setAttachmentMimeTypes(attachmentMimeTypes);
             }
         }
     }
 
-    private void setText(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setText(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_TEXT) != 0) {
             String hasText = "";
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
@@ -658,40 +662,44 @@
                         hasText = "no";
                     }
                 }
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 hasText = "yes";
             }
-            if (V) Log.d(TAG, "setText: " + hasText);
+            if (V) {
+                Log.d(TAG, "setText: " + hasText);
+            }
             e.setText(hasText);
         }
     }
 
-    private void setReceptionStatus(BluetoothMapMessageListingElement e, Cursor c,
-        FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setReceptionStatus(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_RECEPTION_STATUS) != 0) {
             String status = "complete";
-            if (V) Log.d(TAG, "setReceptionStatus: " + status);
+            if (V) {
+                Log.d(TAG, "setReceptionStatus: " + status);
+            }
             e.setReceptionStatus(status);
         }
     }
 
-    private void setDeliveryStatus(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setDeliveryStatus(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_DELIVERY_STATUS) != 0) {
             String deliveryStatus = "delivered";
             // TODO: Should be handled for SMS and MMS as well
-            if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                fi.mMsgType == FilterInfo.TYPE_IM) {
+            if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 deliveryStatus = c.getString(fi.mMessageColDelivery);
             }
-            if (V) Log.d(TAG, "setDeliveryStatus: " + deliveryStatus);
+            if (V) {
+                Log.d(TAG, "setDeliveryStatus: " + deliveryStatus);
+            }
             e.setDeliveryStatus(deliveryStatus);
         }
     }
 
-    private void setSize(BluetoothMapMessageListingElement e, Cursor c,
-        FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setSize(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_SIZE) != 0) {
             int size = 0;
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
@@ -701,34 +709,42 @@
                 size = c.getInt(fi.mMmsColSize);
                 //MMS complete size = attachment_size + subject length
                 String subject = e.getSubject();
-                if (subject == null || subject.length() == 0 ) {
+                if (subject == null || subject.length() == 0) {
                     // Handle setSubject if not done case
                     setSubject(e, c, fi, ap);
                 }
-                if (subject != null && subject.length() != 0 )
+                if (subject != null && subject.length() != 0) {
                     size += subject.length();
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
+                }
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 size = c.getInt(fi.mMessageColSize);
             }
-            if(size <= 0) {
+            if (size <= 0) {
                 // A message cannot have size 0
                 // Hence the size in the database must be wrong.
                 // Set size to 1 to indicate to the client, that the message has content.
-                if (D) Log.d(TAG, "Error in message database, size reported as: " + size
-                        + " Changing size to 1");
+                if (D) {
+                    Log.d(TAG, "Error in message database, size reported as: " + size
+                            + " Changing size to 1");
+                }
                 size = 1;
             }
-            if (V) Log.d(TAG, "setSize: " + size);
+            if (V) {
+                Log.d(TAG, "setSize: " + size);
+            }
             e.setSize(size);
         }
     }
 
     private TYPE getType(Cursor c, FilterInfo fi) {
         TYPE type = null;
-        if (V) Log.d(TAG, "getType: for filterMsgType" + fi.mMsgType);
+        if (V) {
+            Log.d(TAG, "getType: for filterMsgType" + fi.mMsgType);
+        }
         if (fi.mMsgType == FilterInfo.TYPE_SMS) {
-            if (V) Log.d(TAG, "getType: phoneType for SMS " + fi.mPhoneType);
+            if (V) {
+                Log.d(TAG, "getType: phoneType for SMS " + fi.mPhoneType);
+            }
             if (fi.mPhoneType == TelephonyManager.PHONE_TYPE_CDMA) {
                 type = TYPE.SMS_CDMA;
             } else {
@@ -741,64 +757,71 @@
         } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
             type = TYPE.IM;
         }
-        if (V) Log.d(TAG, "getType: " + type);
+        if (V) {
+            Log.d(TAG, "getType: " + type);
+        }
 
         return type;
     }
-    private void setFolderType(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+
+    private void setFolderType(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_FOLDER_TYPE) != 0) {
             String folderType = null;
             int folderId = 0;
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 folderId = c.getInt(fi.mSmsColFolder);
-                if (folderId == 1)
+                if (folderId == 1) {
                     folderType = BluetoothMapContract.FOLDER_NAME_INBOX;
-                else if (folderId == 2)
+                } else if (folderId == 2) {
                     folderType = BluetoothMapContract.FOLDER_NAME_SENT;
-                else if (folderId == 3)
+                } else if (folderId == 3) {
                     folderType = BluetoothMapContract.FOLDER_NAME_DRAFT;
-                else if (folderId == 4 || folderId == 5 || folderId == 6)
+                } else if (folderId == 4 || folderId == 5 || folderId == 6) {
                     folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX;
-                else
+                } else {
                     folderType = BluetoothMapContract.FOLDER_NAME_DELETED;
+                }
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 folderId = c.getInt(fi.mMmsColFolder);
-                if (folderId == 1)
+                if (folderId == 1) {
                     folderType = BluetoothMapContract.FOLDER_NAME_INBOX;
-                else if (folderId == 2)
+                } else if (folderId == 2) {
                     folderType = BluetoothMapContract.FOLDER_NAME_SENT;
-                else if (folderId == 3)
+                } else if (folderId == 3) {
                     folderType = BluetoothMapContract.FOLDER_NAME_DRAFT;
-                else if (folderId == 4)
+                } else if (folderId == 4) {
                     folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX;
-                else
+                } else {
                     folderType = BluetoothMapContract.FOLDER_NAME_DELETED;
+                }
             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
                 // TODO: need to find name from id and then set folder type
             } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
                 folderId = c.getInt(fi.mMessageColFolder);
-                if (folderId == BluetoothMapContract.FOLDER_ID_INBOX)
+                if (folderId == BluetoothMapContract.FOLDER_ID_INBOX) {
                     folderType = BluetoothMapContract.FOLDER_NAME_INBOX;
-                else if (folderId == BluetoothMapContract.FOLDER_ID_SENT)
+                } else if (folderId == BluetoothMapContract.FOLDER_ID_SENT) {
                     folderType = BluetoothMapContract.FOLDER_NAME_SENT;
-                else if (folderId == BluetoothMapContract.FOLDER_ID_DRAFT)
+                } else if (folderId == BluetoothMapContract.FOLDER_ID_DRAFT) {
                     folderType = BluetoothMapContract.FOLDER_NAME_DRAFT;
-                else if (folderId == BluetoothMapContract.FOLDER_ID_OUTBOX)
+                } else if (folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) {
                     folderType = BluetoothMapContract.FOLDER_NAME_OUTBOX;
-                else if (folderId == BluetoothMapContract.FOLDER_ID_DELETED)
+                } else if (folderId == BluetoothMapContract.FOLDER_ID_DELETED) {
                     folderType = BluetoothMapContract.FOLDER_NAME_DELETED;
-                else
+                } else {
                     folderType = BluetoothMapContract.FOLDER_NAME_OTHER;
+                }
             }
-            if (V) Log.d(TAG, "setFolderType: " + folderType);
+            if (V) {
+                Log.d(TAG, "setFolderType: " + folderType);
+            }
             e.setFolderType(folderType);
         }
     }
 
- private String getRecipientNameEmail(BluetoothMapMessageListingElement e,
-                                      Cursor c,
-                                      FilterInfo fi) {
+    private String getRecipientNameEmail(BluetoothMapMessageListingElement e, Cursor c,
+            FilterInfo fi) {
 
         String toAddress, ccAddress, bccAddress;
         toAddress = c.getString(fi.mMessageColToAddress);
@@ -807,15 +830,21 @@
 
         StringBuilder sb = new StringBuilder();
         if (toAddress != null) {
-            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(toAddress);
+            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(toAddress);
             if (tokens.length != 0) {
-                if(D) Log.d(TAG, "toName count= " + tokens.length);
+                if (D) {
+                    Log.d(TAG, "toName count= " + tokens.length);
+                }
                 int i = 0;
                 boolean first = true;
                 while (i < tokens.length) {
-                    if(V) Log.d(TAG, "ToName = " + tokens[i].toString());
+                    if (V) {
+                        Log.d(TAG, "ToName = " + tokens[i].toString());
+                    }
                     String name = tokens[i].getName();
-                    if(!first) sb.append("; "); //Delimiter
+                    if (!first) {
+                        sb.append("; "); //Delimiter
+                    }
                     sb.append(name);
                     first = false;
                     i++;
@@ -827,15 +856,21 @@
             }
         }
         if (ccAddress != null) {
-            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(ccAddress);
+            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(ccAddress);
             if (tokens.length != 0) {
-                if(D) Log.d(TAG, "ccName count= " + tokens.length);
+                if (D) {
+                    Log.d(TAG, "ccName count= " + tokens.length);
+                }
                 int i = 0;
                 boolean first = true;
                 while (i < tokens.length) {
-                    if(V) Log.d(TAG, "ccName = " + tokens[i].toString());
+                    if (V) {
+                        Log.d(TAG, "ccName = " + tokens[i].toString());
+                    }
                     String name = tokens[i].getName();
-                    if(!first) sb.append("; "); //Delimiter
+                    if (!first) {
+                        sb.append("; "); //Delimiter
+                    }
                     sb.append(name);
                     first = false;
                     i++;
@@ -846,15 +881,21 @@
             }
         }
         if (bccAddress != null) {
-            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(bccAddress);
+            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(bccAddress);
             if (tokens.length != 0) {
-                if(D) Log.d(TAG, "bccName count= " + tokens.length);
+                if (D) {
+                    Log.d(TAG, "bccName count= " + tokens.length);
+                }
                 int i = 0;
                 boolean first = true;
                 while (i < tokens.length) {
-                    if(V) Log.d(TAG, "bccName = " + tokens[i].toString());
+                    if (V) {
+                        Log.d(TAG, "bccName = " + tokens[i].toString());
+                    }
                     String name = tokens[i].getName();
-                    if(!first) sb.append("; "); //Delimiter
+                    if (!first) {
+                        sb.append("; "); //Delimiter
+                    }
                     sb.append(name);
                     first = false;
                     i++;
@@ -864,9 +905,8 @@
         return sb.toString();
     }
 
-    private String getRecipientAddressingEmail(BluetoothMapMessageListingElement e,
-                                               Cursor c,
-                                               FilterInfo fi) {
+    private String getRecipientAddressingEmail(BluetoothMapMessageListingElement e, Cursor c,
+            FilterInfo fi) {
         String toAddress, ccAddress, bccAddress;
         toAddress = c.getString(fi.mMessageColToAddress);
         ccAddress = c.getString(fi.mMessageColCcAddress);
@@ -874,15 +914,21 @@
 
         StringBuilder sb = new StringBuilder();
         if (toAddress != null) {
-            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(toAddress);
+            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(toAddress);
             if (tokens.length != 0) {
-                if(D) Log.d(TAG, "toAddress count= " + tokens.length);
+                if (D) {
+                    Log.d(TAG, "toAddress count= " + tokens.length);
+                }
                 int i = 0;
                 boolean first = true;
                 while (i < tokens.length) {
-                    if(V) Log.d(TAG, "ToAddress = " + tokens[i].toString());
+                    if (V) {
+                        Log.d(TAG, "ToAddress = " + tokens[i].toString());
+                    }
                     String email = tokens[i].getAddress();
-                    if(!first) sb.append("; "); //Delimiter
+                    if (!first) {
+                        sb.append("; "); //Delimiter
+                    }
                     sb.append(email);
                     first = false;
                     i++;
@@ -894,15 +940,21 @@
             }
         }
         if (ccAddress != null) {
-            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(ccAddress);
+            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(ccAddress);
             if (tokens.length != 0) {
-                if(D) Log.d(TAG, "ccAddress count= " + tokens.length);
+                if (D) {
+                    Log.d(TAG, "ccAddress count= " + tokens.length);
+                }
                 int i = 0;
                 boolean first = true;
                 while (i < tokens.length) {
-                    if(V) Log.d(TAG, "ccAddress = " + tokens[i].toString());
+                    if (V) {
+                        Log.d(TAG, "ccAddress = " + tokens[i].toString());
+                    }
                     String email = tokens[i].getAddress();
-                    if(!first) sb.append("; "); //Delimiter
+                    if (!first) {
+                        sb.append("; "); //Delimiter
+                    }
                     sb.append(email);
                     first = false;
                     i++;
@@ -913,15 +965,21 @@
             }
         }
         if (bccAddress != null) {
-            Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(bccAddress);
+            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(bccAddress);
             if (tokens.length != 0) {
-                if(D) Log.d(TAG, "bccAddress count= " + tokens.length);
+                if (D) {
+                    Log.d(TAG, "bccAddress count= " + tokens.length);
+                }
                 int i = 0;
                 boolean first = true;
                 while (i < tokens.length) {
-                    if(V) Log.d(TAG, "bccAddress = " + tokens[i].toString());
+                    if (V) {
+                        Log.d(TAG, "bccAddress = " + tokens[i].toString());
+                    }
                     String email = tokens[i].getAddress();
-                    if(!first) sb.append("; "); //Delimiter
+                    if (!first) {
+                        sb.append("; "); //Delimiter
+                    }
                     sb.append(email);
                     first = false;
                     i++;
@@ -932,12 +990,12 @@
     }
 
     private void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c,
-        FilterInfo fi, BluetoothMapAppParams ap) {
+            FilterInfo fi, BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_RECIPIENT_ADDRESSING) != 0) {
             String address = null;
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 int msgType = c.getInt(fi.mSmsColType);
-                if (msgType == Sms.MESSAGE_TYPE_INBOX ) {
+                if (msgType == Sms.MESSAGE_TYPE_INBOX) {
                     address = fi.mPhoneNum;
                 } else {
                     address = c.getString(c.getColumnIndex(Sms.ADDRESS));
@@ -951,7 +1009,9 @@
                     if (threadIdStr != null) {
                         address = getCanonicalAddressSms(mResolver, Integer.valueOf(threadIdStr));
                     }
-                    if(V)  Log.v(TAG, "threadId = " + threadIdStr + " adress:" + address +"\n");
+                    if (V) {
+                        Log.v(TAG, "threadId = " + threadIdStr + " adress:" + address + "\n");
+                    }
                 }
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 long id = c.getLong(c.getColumnIndex(BaseColumns._ID));
@@ -960,49 +1020,57 @@
                 /* Might be another way to handle addresses */
                 address = getRecipientAddressingEmail(e, c, fi);
             }
-            if (V) Log.v(TAG, "setRecipientAddressing: " + address);
-            if(address == null)
+            if (V) {
+                Log.v(TAG, "setRecipientAddressing: " + address);
+            }
+            if (address == null) {
                 address = "";
+            }
             e.setRecipientAddressing(address);
         }
     }
 
-    private void setRecipientName(BluetoothMapMessageListingElement e, Cursor c,
-        FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setRecipientName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_RECIPIENT_NAME) != 0) {
             String name = null;
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 int msgType = c.getInt(fi.mSmsColType);
                 if (msgType != 1) {
                     String phone = c.getString(fi.mSmsColAddress);
-                    if (phone != null && !phone.isEmpty())
+                    if (phone != null && !phone.isEmpty()) {
                         name = getContactNameFromPhone(phone, mResolver);
+                    }
                 } else {
                     name = fi.mPhoneAlphaTag;
                 }
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 long id = c.getLong(fi.mMmsColId);
                 String phone;
-                if(e.getRecipientAddressing() != null){
+                if (e.getRecipientAddressing() != null) {
                     phone = getAddressMms(mResolver, id, MMS_TO);
                 } else {
                     phone = e.getRecipientAddressing();
                 }
-                if (phone != null && !phone.isEmpty())
+                if (phone != null && !phone.isEmpty()) {
                     name = getContactNameFromPhone(phone, mResolver);
+                }
             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
                 /* Might be another way to handle address and names */
-                name = getRecipientNameEmail(e,c,fi);
+                name = getRecipientNameEmail(e, c, fi);
             }
-            if (V) Log.v(TAG, "setRecipientName: " + name);
-            if(name == null)
+            if (V) {
+                Log.v(TAG, "setRecipientName: " + name);
+            }
+            if (name == null) {
                 name = "";
+            }
             e.setRecipientName(name);
         }
     }
 
-    private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) {
             String address = "";
             String tempAddress;
@@ -1013,7 +1081,7 @@
                 } else {
                     tempAddress = fi.mPhoneNum;
                 }
-                if(tempAddress == null) {
+                if (tempAddress == null) {
                     /* This can only happen on devices with no SIM -
                        hence will typically not have any SMS messages. */
                 } else {
@@ -1023,10 +1091,10 @@
                      * because of the N in compaNy)
                      * Hence we need to check if the number is actually a string with alpha chars.
                      * */
-                    Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress).matches(
-                            "[0-9]*[a-zA-Z]+[0-9]*");
+                    Boolean alpha = PhoneNumberUtils.stripSeparators(tempAddress)
+                            .matches("[0-9]*[a-zA-Z]+[0-9]*");
 
-                    if(address == null || address.length() < 2 || alpha) {
+                    if (address == null || address.length() < 2 || alpha) {
                         address = tempAddress; // if the number is a service acsii text just use it
                     }
                 }
@@ -1034,130 +1102,150 @@
                 long id = c.getLong(fi.mMmsColId);
                 tempAddress = getAddressMms(mResolver, id, MMS_FROM);
                 address = PhoneNumberUtils.extractNetworkPortion(tempAddress);
-                if(address == null || address.length() < 1){
+                if (address == null || address.length() < 1) {
                     address = tempAddress; // if the number is a service acsii text just use it
                 }
             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/* ||
                        fi.mMsgType == FilterInfo.TYPE_IM*/) {
                 String nameEmail = c.getString(fi.mMessageColFromAddress);
-                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
+                Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(nameEmail);
                 if (tokens.length != 0) {
-                    if(D) Log.d(TAG, "Originator count= " + tokens.length);
+                    if (D) {
+                        Log.d(TAG, "Originator count= " + tokens.length);
+                    }
                     int i = 0;
                     boolean first = true;
                     while (i < tokens.length) {
-                        if(V) Log.d(TAG, "SenderAddress = " + tokens[i].toString());
+                        if (V) {
+                            Log.d(TAG, "SenderAddress = " + tokens[i].toString());
+                        }
                         String[] emails = new String[1];
                         emails[0] = tokens[i].getAddress();
                         String name = tokens[i].getName();
-                        if(!first) address += "; "; //Delimiter
+                        if (!first) {
+                            address += "; "; //Delimiter
+                        }
                         address += emails[0];
                         first = false;
                         i++;
                     }
                 }
-            } else if(fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
                 // TODO: For IM we add the contact ID in the addressing
-                long contact_id = c.getLong(fi.mMessageColFromAddress);
+                long contactId = c.getLong(fi.mMessageColFromAddress);
                 // TODO: This is a BAD hack, that we map the contact ID to a conversation ID!!!
                 //       We need to reach a conclusion on what to do
                 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
-                Cursor contacts = mResolver.query(contactsUri,
-                                           BluetoothMapContract.BT_CONTACT_PROJECTION,
-                                           BluetoothMapContract.ConvoContactColumns.CONVO_ID
-                                           + " = " + contact_id, null, null);
+                Cursor contacts =
+                        mResolver.query(contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION,
+                                BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = "
+                                        + contactId, null, null);
                 try {
                     // TODO this will not work for group-chats
-                    if(contacts != null && contacts.moveToFirst()){
-                        address = contacts.getString(
-                                contacts.getColumnIndex(
-                                        BluetoothMapContract.ConvoContactColumns.UCI));
+                    if (contacts != null && contacts.moveToFirst()) {
+                        address = contacts.getString(contacts.getColumnIndex(
+                                BluetoothMapContract.ConvoContactColumns.UCI));
                     }
                 } finally {
-                    if (contacts != null) contacts.close();
+                    if (contacts != null) {
+                        contacts.close();
+                    }
                 }
 
             }
-            if (V) Log.v(TAG, "setSenderAddressing: " + address);
-            if(address == null)
+            if (V) {
+                Log.v(TAG, "setSenderAddressing: " + address);
+            }
+            if (address == null) {
                 address = "";
+            }
             e.setSenderAddressing(address);
         }
     }
 
-    private void setSenderName(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setSenderName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) {
             String name = "";
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
                 if (msgType == 1) {
                     String phone = c.getString(fi.mSmsColAddress);
-                    if (phone != null && !phone.isEmpty())
+                    if (phone != null && !phone.isEmpty()) {
                         name = getContactNameFromPhone(phone, mResolver);
+                    }
                 } else {
                     name = fi.mPhoneAlphaTag;
                 }
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 long id = c.getLong(fi.mMmsColId);
                 String phone;
-                if(e.getSenderAddressing() != null){
+                if (e.getSenderAddressing() != null) {
                     phone = getAddressMms(mResolver, id, MMS_FROM);
                 } else {
                     phone = e.getSenderAddressing();
                 }
-                if (phone != null && !phone.isEmpty() )
+                if (phone != null && !phone.isEmpty()) {
                     name = getContactNameFromPhone(phone, mResolver);
+                }
             } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL/*  ||
                        fi.mMsgType == FilterInfo.TYPE_IM*/) {
                 String nameEmail = c.getString(fi.mMessageColFromAddress);
-                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
+                Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(nameEmail);
                 if (tokens.length != 0) {
-                    if(D) Log.d(TAG, "Originator count= " + tokens.length);
+                    if (D) {
+                        Log.d(TAG, "Originator count= " + tokens.length);
+                    }
                     int i = 0;
                     boolean first = true;
                     while (i < tokens.length) {
-                        if(V) Log.d(TAG, "senderName = " + tokens[i].toString());
+                        if (V) {
+                            Log.d(TAG, "senderName = " + tokens[i].toString());
+                        }
                         String[] emails = new String[1];
                         emails[0] = tokens[i].getAddress();
                         String nameIn = tokens[i].getName();
-                        if(!first) name += "; "; //Delimiter
+                        if (!first) {
+                            name += "; "; //Delimiter
+                        }
                         name += nameIn;
                         first = false;
                         i++;
                     }
                 }
-            } else if(fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_IM) {
                 // For IM we add the contact ID in the addressing
-                long contact_id = c.getLong(fi.mMessageColFromAddress);
+                long contactId = c.getLong(fi.mMessageColFromAddress);
                 Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
-                Cursor contacts = mResolver.query(contactsUri,
-                                           BluetoothMapContract.BT_CONTACT_PROJECTION,
-                                           BluetoothMapContract.ConvoContactColumns.CONVO_ID
-                                           + " = " + contact_id, null, null);
+                Cursor contacts =
+                        mResolver.query(contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION,
+                                BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = "
+                                        + contactId, null, null);
                 try {
                     // TODO this will not work for group-chats
-                    if(contacts != null && contacts.moveToFirst()){
-                        name = contacts.getString(
-                                contacts.getColumnIndex(
-                                        BluetoothMapContract.ConvoContactColumns.NAME));
+                    if (contacts != null && contacts.moveToFirst()) {
+                        name = contacts.getString(contacts.getColumnIndex(
+                                BluetoothMapContract.ConvoContactColumns.NAME));
                     }
                 } finally {
-                    if (contacts != null) contacts.close();
+                    if (contacts != null) {
+                        contacts.close();
+                    }
                 }
             }
-            if (V) Log.v(TAG, "setSenderName: " + name);
-            if(name == null)
+            if (V) {
+                Log.v(TAG, "setSenderName: " + name);
+            }
+            if (name == null) {
                 name = "";
+            }
             e.setSenderName(name);
         }
     }
 
 
-
-
-    private void setDateTime(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setDateTime(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         if ((ap.getParameterMask() & MASK_DATETIME) != 0) {
             long date = 0;
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
@@ -1173,8 +1261,7 @@
                 /* } else { */
                 /*     date = c.getLong(c.getColumnIndex(Mms.DATE_SENT)) * 1000L; */
                 /* } */
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 date = c.getLong(fi.mMessageColDate);
             }
             e.setDateTime(date);
@@ -1182,56 +1269,61 @@
     }
 
 
-    private void setLastActivity(BluetoothMapConvoListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setLastActivity(BluetoothMapConvoListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         long date = 0;
-        if (fi.mMsgType == FilterInfo.TYPE_SMS ||
-                fi.mMsgType == FilterInfo.TYPE_MMS ) {
+        if (fi.mMsgType == FilterInfo.TYPE_SMS || fi.mMsgType == FilterInfo.TYPE_MMS) {
             date = c.getLong(MMS_SMS_THREAD_COL_DATE);
-        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
-                fi.mMsgType == FilterInfo.TYPE_IM) {
+        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
             date = c.getLong(fi.mConvoColLastActivity);
         }
         e.setLastActivity(date);
-        if (V) Log.v(TAG, "setDateTime: " + e.getLastActivityString());
+        if (V) {
+            Log.v(TAG, "setDateTime: " + e.getLastActivityString());
+        }
 
     }
 
-    static public String getTextPartsMms(ContentResolver r, long id) {
+    public static String getTextPartsMms(ContentResolver r, long id) {
         String text = "";
         String selection = new String("mid=" + id);
         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part");
         Uri uriAddress = Uri.parse(uriStr);
         // TODO: maybe use a projection with only "ct" and "text"
-        Cursor c = r.query(uriAddress, null, selection,
-            null, null);
+        Cursor c = r.query(uriAddress, null, selection, null, null);
         try {
             if (c != null && c.moveToFirst()) {
                 do {
                     String ct = c.getString(c.getColumnIndex("ct"));
                     if (ct.equals("text/plain")) {
                         String part = c.getString(c.getColumnIndex("text"));
-                        if(part != null) {
+                        if (part != null) {
                             text += part;
                         }
                     }
-                } while(c.moveToNext());
+                } while (c.moveToNext());
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
 
         return text;
     }
 
-    private void setSubject(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setSubject(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         String subject = "";
         int subLength = ap.getSubjectLength();
-        if(subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+        if (subLength == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
             subLength = 256;
+        }
 
-        if ((ap.getParameterMask() & MASK_SUBJECT) != 0) {
+        // Fix Subject Display issue with HONDA Carkit - Ignore subject Mask.
+        if (DeviceWorkArounds.addressStartsWith(BluetoothMapService.getRemoteDevice().getAddress(),
+                    DeviceWorkArounds.HONDA_CARKIT)
+                || (ap.getParameterMask() & MASK_SUBJECT) != 0) {
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 subject = c.getString(fi.mSmsColSubject);
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
@@ -1241,32 +1333,34 @@
                     long id = c.getLong(fi.mMmsColId);
                     subject = getTextPartsMms(mResolver, id);
                 }
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL  ||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 subject = c.getString(fi.mMessageColSubject);
             }
             if (subject != null && subject.length() > subLength) {
                 subject = subject.substring(0, subLength);
-            } else if (subject == null ) {
+            } else if (subject == null) {
                 subject = "";
             }
-            if (V) Log.d(TAG, "setSubject: " + subject);
+            if (V) {
+                Log.d(TAG, "setSubject: " + subject);
+            }
             e.setSubject(subject);
         }
     }
 
-    private void setHandle(BluetoothMapMessageListingElement e, Cursor c,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private void setHandle(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         long handle = -1;
         if (fi.mMsgType == FilterInfo.TYPE_SMS) {
             handle = c.getLong(fi.mSmsColId);
         } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
             handle = c.getLong(fi.mMmsColId);
-        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                   fi.mMsgType == FilterInfo.TYPE_IM) {
+        } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
             handle = c.getLong(fi.mMessageColId);
         }
-        if (V) Log.d(TAG, "setHandle: " + handle );
+        if (V) {
+            Log.d(TAG, "setHandle: " + handle);
+        }
         e.setHandle(handle);
     }
 
@@ -1275,7 +1369,7 @@
         BluetoothMapMessageListingElement e = new BluetoothMapMessageListingElement();
         setHandle(e, c, fi, ap);
         setDateTime(e, c, fi, ap);
-        e.setType(getType(c, fi), ((ap.getParameterMask() & MASK_TYPE) != 0) ? true : false);
+        e.setType(getType(c, fi), (ap.getParameterMask() & MASK_TYPE) != 0);
         setRead(e, c, fi, ap);
         // we set number and name for sender/recipient later
         // they require lookup on contacts so no need to
@@ -1303,8 +1397,8 @@
             return name;
         }
 
-        Uri uri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
-                Uri.encode(phone));
+        Uri uri =
+                Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phone));
 
         String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
         String selection = Contacts.IN_VISIBLE_GROUP + "=1";
@@ -1312,7 +1406,7 @@
         Cursor c = null;
         try {
             c = resolver.query(uri, projection, selection, null, orderBy);
-            if(c != null) {
+            if (c != null) {
                 int colIndex = c.getColumnIndex(Contacts.DISPLAY_NAME);
                 if (c.getCount() >= 1) {
                     c.moveToFirst();
@@ -1320,16 +1414,21 @@
                 }
             }
         } finally {
-            if(c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
         return name;
     }
+
+    private static final String[] RECIPIENT_ID_PROJECTION = {Threads.RECIPIENT_IDS};
+
     /**
      * Get SMS RecipientAddresses for DRAFT folder based on threadId
      *
-    */
-    static public String getCanonicalAddressSms(ContentResolver r,  int threadId) {
-       String [] RECIPIENT_ID_PROJECTION = { Threads.RECIPIENT_IDS };
+     */
+    public static String getCanonicalAddressSms(ContentResolver r, int threadId) {
+
         /*
          1. Get Recipient Ids from Threads.CONTENT_URI
          2. Get Recipient Address for corresponding Id from canonical-addresses table.
@@ -1343,54 +1442,68 @@
         Cursor cr = null;
         String recipientAddress = "";
         String recipientIds = null;
-        String whereClause = "_id="+threadId;
-        if (V) Log.v(TAG, "whereClause is "+ whereClause);
+        String whereClause = "_id=" + threadId;
+        if (V) {
+            Log.v(TAG, "whereClause is " + whereClause);
+        }
         try {
             cr = r.query(sAllThreadsUri, RECIPIENT_ID_PROJECTION, whereClause, null, null);
             if (cr != null && cr.moveToFirst()) {
                 recipientIds = cr.getString(0);
-                if (V) Log.v(TAG, "cursor.getCount(): " + cr.getCount() + "recipientIds: "
-                        + recipientIds + "selection: "+ whereClause );
+                if (V) {
+                    Log.v(TAG,
+                            "cursor.getCount(): " + cr.getCount() + "recipientIds: " + recipientIds
+                                    + "selection: " + whereClause);
+                }
             }
         } finally {
-            if(cr != null) {
+            if (cr != null) {
                 cr.close();
                 cr = null;
             }
         }
-        if (V) Log.v(TAG, "recipientIds with spaces: "+ recipientIds +"\n");
-        if(recipientIds != null) {
-            String recipients[] = null;
+        if (V) {
+            Log.v(TAG, "recipientIds with spaces: " + recipientIds + "\n");
+        }
+        if (recipientIds != null) {
+            String[] recipients = null;
             whereClause = "";
             recipients = recipientIds.split(" ");
-            for (String id: recipients) {
-                if(whereClause.length() != 0)
-                    whereClause +=" OR ";
-                whereClause +="_id="+id;
+            for (String id : recipients) {
+                if (whereClause.length() != 0) {
+                    whereClause += " OR ";
+                }
+                whereClause += "_id=" + id;
             }
-            if (V) Log.v(TAG, "whereClause is "+ whereClause);
+            if (V) {
+                Log.v(TAG, "whereClause is " + whereClause);
+            }
             try {
-                cr = r.query(sAllCanonical , null, whereClause, null, null);
+                cr = r.query(sAllCanonical, null, whereClause, null, null);
                 if (cr != null && cr.moveToFirst()) {
                     do {
                         //TODO: Multiple Recipeints are appended with ";" for now.
-                        if(recipientAddress.length() != 0 )
-                           recipientAddress+=";";
-                        recipientAddress += cr.getString(
-                                cr.getColumnIndex(CanonicalAddressesColumns.ADDRESS));
-                    } while(cr.moveToNext());
+                        if (recipientAddress.length() != 0) {
+                            recipientAddress += ";";
+                        }
+                        recipientAddress +=
+                                cr.getString(cr.getColumnIndex(CanonicalAddressesColumns.ADDRESS));
+                    } while (cr.moveToNext());
                 }
-           } finally {
-               if(cr != null)
-                   cr.close();
-           }
+            } finally {
+                if (cr != null) {
+                    cr.close();
+                }
+            }
         }
 
-        if(V) Log.v(TAG,"Final recipientAddress : "+ recipientAddress);
+        if (V) {
+            Log.v(TAG, "Final recipientAddress : " + recipientAddress);
+        }
         return recipientAddress;
-     }
+    }
 
-    static public String getAddressMms(ContentResolver r, long id, int type) {
+    public static String getAddressMms(ContentResolver r, long id, int type) {
         String selection = new String("msg_id=" + id + " AND type=" + type);
         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/addr");
         Uri uriAddress = Uri.parse(uriStr);
@@ -1401,15 +1514,17 @@
             c = r.query(uriAddress, projection, selection, null, null); // TODO: Add projection
             int colIndex = c.getColumnIndex(Mms.Addr.ADDRESS);
             if (c != null) {
-                if(c.moveToFirst()) {
+                if (c.moveToFirst()) {
                     addr = c.getString(colIndex);
-                    if(addr.equals(INSERT_ADDRES_TOKEN)) {
-                        addr  = "";
+                    if (addr.equals(INSERT_ADDRES_TOKEN)) {
+                        addr = "";
                     }
                 }
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
         return addr;
     }
@@ -1424,12 +1539,16 @@
         String phone = getAddressMms(mResolver, id, MMS_TO);
         if (phone != null && phone.length() > 0) {
             if (phone.matches(recip)) {
-                if (V) Log.v(TAG, "matchRecipientMms: match recipient phone = " + phone);
+                if (V) {
+                    Log.v(TAG, "matchRecipientMms: match recipient phone = " + phone);
+                }
                 res = true;
             } else {
                 String name = getContactNameFromPhone(phone, mResolver);
                 if (name != null && name.length() > 0 && name.matches(recip)) {
-                    if (V) Log.v(TAG, "matchRecipientMms: match recipient name = " + name);
+                    if (V) {
+                        Log.v(TAG, "matchRecipientMms: match recipient name = " + name);
+                    }
                     res = true;
                 } else {
                     res = false;
@@ -1448,10 +1567,14 @@
             String phone = fi.mPhoneNum;
             String name = fi.mPhoneAlphaTag;
             if (phone != null && phone.length() > 0 && phone.matches(recip)) {
-                if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
+                if (V) {
+                    Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
+                }
                 res = true;
             } else if (name != null && name.length() > 0 && name.matches(recip)) {
-                if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
+                if (V) {
+                    Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
+                }
                 res = true;
             } else {
                 res = false;
@@ -1460,12 +1583,16 @@
             String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
             if (phone != null && phone.length() > 0) {
                 if (phone.matches(recip)) {
-                    if (V) Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
+                    if (V) {
+                        Log.v(TAG, "matchRecipientSms: match recipient phone = " + phone);
+                    }
                     res = true;
                 } else {
                     String name = getContactNameFromPhone(phone, mResolver);
                     if (name != null && name.length() > 0 && name.matches(recip)) {
-                        if (V) Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
+                        if (V) {
+                            Log.v(TAG, "matchRecipientSms: match recipient name = " + name);
+                        }
                         res = true;
                     } else {
                         res = false;
@@ -1489,7 +1616,9 @@
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 res = matchRecipientMms(c, fi, recip);
             } else {
-                if (D) Log.d(TAG, "matchRecipient: Unknown msg type: " + fi.mMsgType);
+                if (D) {
+                    Log.d(TAG, "matchRecipient: Unknown msg type: " + fi.mMsgType);
+                }
                 res = false;
             }
         } else {
@@ -1504,12 +1633,16 @@
         String phone = getAddressMms(mResolver, id, MMS_FROM);
         if (phone != null && phone.length() > 0) {
             if (phone.matches(orig)) {
-                if (V) Log.v(TAG, "matchOriginatorMms: match originator phone = " + phone);
+                if (V) {
+                    Log.v(TAG, "matchOriginatorMms: match originator phone = " + phone);
+                }
                 res = true;
             } else {
                 String name = getContactNameFromPhone(phone, mResolver);
                 if (name != null && name.length() > 0 && name.matches(orig)) {
-                    if (V) Log.v(TAG, "matchOriginatorMms: match originator name = " + name);
+                    if (V) {
+                        Log.v(TAG, "matchOriginatorMms: match originator name = " + name);
+                    }
                     res = true;
                 } else {
                     res = false;
@@ -1526,14 +1659,18 @@
         int msgType = c.getInt(c.getColumnIndex(Sms.TYPE));
         if (msgType == 1) {
             String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
-            if (phone !=null && phone.length() > 0) {
+            if (phone != null && phone.length() > 0) {
                 if (phone.matches(orig)) {
-                    if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
+                    if (V) {
+                        Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
+                    }
                     res = true;
                 } else {
                     String name = getContactNameFromPhone(phone, mResolver);
                     if (name != null && name.length() > 0 && name.matches(orig)) {
-                        if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
+                        if (V) {
+                            Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
+                        }
                         res = true;
                     } else {
                         res = false;
@@ -1546,10 +1683,14 @@
             String phone = fi.mPhoneNum;
             String name = fi.mPhoneAlphaTag;
             if (phone != null && phone.length() > 0 && phone.matches(orig)) {
-                if (V) Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
+                if (V) {
+                    Log.v(TAG, "matchOriginatorSms: match originator phone = " + phone);
+                }
                 res = true;
             } else if (name != null && name.length() > 0 && name.matches(orig)) {
-                if (V) Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
+                if (V) {
+                    Log.v(TAG, "matchOriginatorSms: match originator name = " + name);
+                }
                 res = true;
             } else {
                 res = false;
@@ -1558,7 +1699,7 @@
         return res;
     }
 
-   private boolean matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
+    private boolean matchOriginator(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
         boolean res;
         String orig = ap.getFilterOriginator();
         if (orig != null && orig.length() > 0) {
@@ -1569,7 +1710,9 @@
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 res = matchOriginatorMms(c, fi, orig);
             } else {
-                if(D) Log.d(TAG, "matchOriginator: Unknown msg type: " + fi.mMsgType);
+                if (D) {
+                    Log.d(TAG, "matchOriginator: Unknown msg type: " + fi.mMsgType);
+                }
                 res = false;
             }
         } else {
@@ -1579,11 +1722,7 @@
     }
 
     private boolean matchAddresses(Cursor c, FilterInfo fi, BluetoothMapAppParams ap) {
-        if (matchOriginator(c, fi, ap) && matchRecipient(c, fi, ap)) {
-            return true;
-        } else {
-            return false;
-        }
+        return matchOriginator(c, fi, ap) && matchRecipient(c, fi, ap);
     }
 
     /*
@@ -1594,13 +1733,13 @@
         if (BluetoothMapContract.FOLDER_NAME_INBOX.equalsIgnoreCase(folder)) {
             where = Sms.TYPE + " = 1 AND " + Sms.THREAD_ID + " <> -1";
         } else if (BluetoothMapContract.FOLDER_NAME_OUTBOX.equalsIgnoreCase(folder)) {
-            where = "(" + Sms.TYPE + " = 4 OR " + Sms.TYPE + " = 5 OR "
-                    + Sms.TYPE + " = 6) AND " + Sms.THREAD_ID + " <> -1";
+            where = "(" + Sms.TYPE + " = 4 OR " + Sms.TYPE + " = 5 OR " + Sms.TYPE + " = 6) AND "
+                    + Sms.THREAD_ID + " <> -1";
         } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) {
             where = Sms.TYPE + " = 2 AND " + Sms.THREAD_ID + " <> -1";
         } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) {
-            where = Sms.TYPE + " = 3 AND " +
-                "(" + Sms.THREAD_ID + " IS NULL OR " + Sms.THREAD_ID + " <> -1 )";
+            where = Sms.TYPE + " = 3 AND " + "(" + Sms.THREAD_ID + " IS NULL OR " + Sms.THREAD_ID
+                    + " <> -1 )";
         } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) {
             where = Sms.THREAD_ID + " = -1";
         }
@@ -1617,8 +1756,8 @@
         } else if (BluetoothMapContract.FOLDER_NAME_SENT.equalsIgnoreCase(folder)) {
             where = Mms.MESSAGE_BOX + " = 2 AND " + Mms.THREAD_ID + " <> -1";
         } else if (BluetoothMapContract.FOLDER_NAME_DRAFT.equalsIgnoreCase(folder)) {
-            where = Mms.MESSAGE_BOX + " = 3 AND " +
-                "(" + Mms.THREAD_ID + " IS NULL OR " + Mms.THREAD_ID + " <> -1 )";
+            where = Mms.MESSAGE_BOX + " = 3 AND " + "(" + Mms.THREAD_ID + " IS NULL OR "
+                    + Mms.THREAD_ID + " <> -1 )";
         } else if (BluetoothMapContract.FOLDER_NAME_DELETED.equalsIgnoreCase(folder)) {
             where = Mms.THREAD_ID + " = -1";
         }
@@ -1631,7 +1770,7 @@
         if (folderId >= 0) {
             where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId;
         } else {
-            Log.e(TAG, "setWhereFilterFolderTypeEmail: not valid!" );
+            Log.e(TAG, "setWhereFilterFolderTypeEmail: not valid!");
             throw new IllegalArgumentException("Invalid folder ID");
         }
         return where;
@@ -1642,14 +1781,14 @@
         if (folderId > BluetoothMapContract.FOLDER_ID_OTHER) {
             where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + folderId;
         } else {
-            Log.e(TAG, "setWhereFilterFolderTypeIm: not valid!" );
+            Log.e(TAG, "setWhereFilterFolderTypeIm: not valid!");
             throw new IllegalArgumentException("Invalid folder ID");
         }
         return where;
     }
 
     private String setWhereFilterFolderType(BluetoothMapFolderElement folderElement,
-                                            FilterInfo fi) {
+            FilterInfo fi) {
         String where = "1=1";
         if (!folderElement.shouldIgnore()) {
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
@@ -1685,8 +1824,7 @@
                 if ((ap.getFilterReadStatus() & 0x02) != 0) {
                     where = " AND " + Mms.READ + "= 1";
                 }
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 if ((ap.getFilterReadStatus() & 0x01) != 0) {
                     where = " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "= 0";
                 }
@@ -1706,10 +1844,9 @@
                 where = " AND " + Sms.DATE + " >= " + ap.getFilterPeriodBegin();
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 where = " AND " + Mms.DATE + " >= " + (ap.getFilterPeriodBegin() / 1000L);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
-                where = " AND " + BluetoothMapContract.MessageColumns.DATE +
-                        " >= " + (ap.getFilterPeriodBegin());
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
+                where = " AND " + BluetoothMapContract.MessageColumns.DATE + " >= "
+                        + (ap.getFilterPeriodBegin());
             }
         }
 
@@ -1718,25 +1855,24 @@
                 where += " AND " + Sms.DATE + " < " + ap.getFilterPeriodEnd();
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
-                where += " AND " + BluetoothMapContract.MessageColumns.DATE +
-                        " < " + (ap.getFilterPeriodEnd());
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
+                where += " AND " + BluetoothMapContract.MessageColumns.DATE + " < "
+                        + (ap.getFilterPeriodEnd());
             }
         }
         return where;
     }
+
     private String setWhereFilterLastActivity(BluetoothMapAppParams ap, FilterInfo fi) {
-            String where = "";
+        String where = "";
         if ((ap.getFilterLastActivityBegin() != -1)) {
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 where = " AND " + Sms.DATE + " >= " + ap.getFilterLastActivityBegin();
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 where = " AND " + Mms.DATE + " >= " + (ap.getFilterLastActivityBegin() / 1000L);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||
-                      fi.mMsgType == FilterInfo.TYPE_IM ) {
-                where = " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY +
-                        " >= " + (ap.getFilterPeriodBegin());
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
+                where = " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+                        + " >= " + (ap.getFilterPeriodBegin());
             }
         }
         if ((ap.getFilterLastActivityEnd() != -1)) {
@@ -1744,9 +1880,9 @@
                 where += " AND " + Sms.DATE + " < " + ap.getFilterLastActivityEnd();
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 where += " AND " + Mms.DATE + " < " + (ap.getFilterPeriodEnd() / 1000L);
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL||fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 where += " AND " + BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
-                      + " < " + (ap.getFilterLastActivityEnd());
+                        + " < " + (ap.getFilterLastActivityEnd());
             }
         }
         return where;
@@ -1760,8 +1896,8 @@
         /* Be aware of wild cards in the beginning of string, may not be valid? */
         if (orig != null && orig.length() > 0) {
             orig = orig.replace("*", "%");
-            where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
-                    + " LIKE '%" +  orig + "%'";
+            where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST + " LIKE '%" + orig
+                    + "%'";
         }
         return where;
     }
@@ -1773,8 +1909,8 @@
         /* Be aware of wild cards in the beginning of string, may not be valid? */
         if (orig != null && orig.length() > 0) {
             orig = orig.replace("*", "%");
-            where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST
-                    + " LIKE '%" +  orig + "%'";
+            where = " AND " + BluetoothMapContract.MessageColumns.FROM_LIST + " LIKE '%" + orig
+                    + "%'";
         }
         return where;
     }
@@ -1783,24 +1919,18 @@
         String where = "";
         int pri = ap.getFilterPriority();
         /*only MMS have priority info */
-        if(fi.mMsgType == FilterInfo.TYPE_MMS)
-        {
-            if(pri == 0x0002)
-            {
-                where += " AND " + Mms.PRIORITY + "<=" +
-                    Integer.toString(PduHeaders.PRIORITY_NORMAL);
-            }else if(pri == 0x0001) {
-                where += " AND " + Mms.PRIORITY + "=" +
-                    Integer.toString(PduHeaders.PRIORITY_HIGH);
+        if (fi.mMsgType == FilterInfo.TYPE_MMS) {
+            if (pri == 0x0002) {
+                where += " AND " + Mms.PRIORITY + "<=" + Integer.toString(
+                        PduHeaders.PRIORITY_NORMAL);
+            } else if (pri == 0x0001) {
+                where += " AND " + Mms.PRIORITY + "=" + Integer.toString(PduHeaders.PRIORITY_HIGH);
             }
         }
-        if(fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-           fi.mMsgType == FilterInfo.TYPE_IM)
-        {
-            if(pri == 0x0002)
-            {
+        if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
+            if (pri == 0x0002) {
                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "!=1";
-            }else if(pri == 0x0001) {
+            } else if (pri == 0x0001) {
                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + "=1";
             }
         }
@@ -1815,10 +1945,10 @@
         /* Be aware of wild cards in the beginning of string, may not be valid? */
         if (recip != null && recip.length() > 0) {
             recip = recip.replace("*", "%");
-            where = " AND ("
-            + BluetoothMapContract.MessageColumns.TO_LIST  + " LIKE '%" + recip + "%' OR "
-            + BluetoothMapContract.MessageColumns.CC_LIST  + " LIKE '%" + recip + "%' OR "
-            + BluetoothMapContract.MessageColumns.BCC_LIST + " LIKE '%" + recip + "%' )";
+            where = " AND (" + BluetoothMapContract.MessageColumns.TO_LIST + " LIKE '%" + recip
+                    + "%' OR " + BluetoothMapContract.MessageColumns.CC_LIST + " LIKE '%" + recip
+                    + "%' OR " + BluetoothMapContract.MessageColumns.BCC_LIST + " LIKE '%" + recip
+                    + "%' )";
         }
         return where;
     }
@@ -1827,17 +1957,18 @@
         String where = "";
         long id = -1;
         String msgHandle = ap.getFilterMsgHandleString();
-        if(msgHandle != null) {
+        if (msgHandle != null) {
             id = BluetoothMapUtils.getCpHandle(msgHandle);
-            if(D)Log.d(TAG,"id: " + id);
+            if (D) {
+                Log.d(TAG, "id: " + id);
+            }
         }
-        if(id != -1) {
+        if (id != -1) {
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
-               where = " AND " + Sms._ID + " = " + id;
+                where = " AND " + Sms._ID + " = " + id;
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 where = " AND " + Mms._ID + " = " + id;
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 where = " AND " + BluetoothMapContract.MessageColumns._ID + " = " + id;
             }
         }
@@ -1848,17 +1979,18 @@
         String where = "";
         long id = -1;
         String msgHandle = ap.getFilterConvoIdString();
-        if(msgHandle != null) {
+        if (msgHandle != null) {
             id = BluetoothMapUtils.getMsgHandleAsLong(msgHandle);
-            if(D)Log.d(TAG,"id: " + id);
+            if (D) {
+                Log.d(TAG, "id: " + id);
+            }
         }
-        if(id > 0) {
+        if (id > 0) {
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
-               where = " AND " + Sms.THREAD_ID + " = " + id;
+                where = " AND " + Sms.THREAD_ID + " = " + id;
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
                 where = " AND " + Mms.THREAD_ID + " = " + id;
-            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL ||
-                       fi.mMsgType == FilterInfo.TYPE_IM) {
+            } else if (fi.mMsgType == FilterInfo.TYPE_EMAIL || fi.mMsgType == FilterInfo.TYPE_IM) {
                 where = " AND " + BluetoothMapContract.MessageColumns.THREAD_ID + " = " + id;
             }
         }
@@ -1866,16 +1998,16 @@
         return where;
     }
 
-    private String setWhereFilter(BluetoothMapFolderElement folderElement,
-            FilterInfo fi, BluetoothMapAppParams ap) {
+    private String setWhereFilter(BluetoothMapFolderElement folderElement, FilterInfo fi,
+            BluetoothMapAppParams ap) {
         String where = "";
         where += setWhereFilterFolderType(folderElement, fi);
 
         String msgHandleWhere = setWhereFilterMessageHandle(ap, fi);
         /* if message handle filter is available, the other filters should be ignored */
-        if(msgHandleWhere.isEmpty()) {
+        if (msgHandleWhere.isEmpty()) {
             where += setWhereFilterReadStatus(ap, fi);
-            where += setWhereFilterPriority(ap,fi);
+            where += setWhereFilterPriority(ap, fi);
             where += setWhereFilterPeriod(ap, fi);
             if (fi.mMsgType == FilterInfo.TYPE_EMAIL) {
                 where += setWhereFilterOriginatorEmail(ap);
@@ -1901,7 +2033,7 @@
         if (smsSelected(fi, ap) || mmsSelected(ap)) {
 
             // Filter Read Status
-            if(ap.getFilterReadStatus() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+            if (ap.getFilterReadStatus() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 if ((ap.getFilterReadStatus() & FILTER_READ_STATUS_UNREAD_ONLY) != 0) {
                     selection.append(" AND ").append(Threads.READ).append(" = 0");
                 }
@@ -1911,29 +2043,35 @@
             }
 
             // Filter time
-            if ((ap.getFilterLastActivityBegin() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)){
-                selection.append(" AND ").append(Threads.DATE).append(" >= ")
-                .append(ap.getFilterLastActivityBegin());
+            if ((ap.getFilterLastActivityBegin()
+                    != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) {
+                selection.append(" AND ")
+                        .append(Threads.DATE)
+                        .append(" >= ")
+                        .append(ap.getFilterLastActivityBegin());
             }
             if ((ap.getFilterLastActivityEnd() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER)) {
-                selection.append(" AND ").append(Threads.DATE).append(" <= ")
-                .append(ap.getFilterLastActivityEnd());
+                selection.append(" AND ")
+                        .append(Threads.DATE)
+                        .append(" <= ")
+                        .append(ap.getFilterLastActivityEnd());
             }
 
             // Filter ConvoId
             long convoId = -1;
-            if(ap.getFilterConvoId() != null) {
+            if (ap.getFilterConvoId() != null) {
                 convoId = ap.getFilterConvoId().getLeastSignificantBits();
             }
-            if(convoId > 0) {
-                selection.append(" AND ").append(Threads._ID).append(" = ")
-                .append(Long.toString(convoId));
+            if (convoId > 0) {
+                selection.append(" AND ")
+                        .append(Threads._ID)
+                        .append(" = ")
+                        .append(Long.toString(convoId));
             }
         }
     }
 
 
-
     /**
      * Determine from application parameter if sms should be included.
      * The filter mask is set for message types not selected
@@ -1945,22 +2083,28 @@
         int msgType = ap.getFilterMessageType();
         int phoneType = fi.mPhoneType;
 
-        if (D) Log.d(TAG, "smsSelected msgType: " + msgType);
+        if (D) {
+            Log.d(TAG, "smsSelected msgType: " + msgType);
+        }
 
-        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
             return true;
+        }
 
         if ((msgType & (BluetoothMapAppParams.FILTER_NO_SMS_CDMA
-                |BluetoothMapAppParams.FILTER_NO_SMS_GSM)) == 0)
+                | BluetoothMapAppParams.FILTER_NO_SMS_GSM)) == 0) {
             return true;
+        }
 
-        if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_GSM) == 0)
-                && (phoneType == TelephonyManager.PHONE_TYPE_GSM))
+        if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_GSM) == 0) && (phoneType
+                == TelephonyManager.PHONE_TYPE_GSM)) {
             return true;
+        }
 
-        if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_CDMA) == 0)
-                && (phoneType == TelephonyManager.PHONE_TYPE_CDMA))
+        if (((msgType & BluetoothMapAppParams.FILTER_NO_SMS_CDMA) == 0) && (phoneType
+                == TelephonyManager.PHONE_TYPE_CDMA)) {
             return true;
+        }
 
         return false;
     }
@@ -1975,13 +2119,17 @@
     private boolean mmsSelected(BluetoothMapAppParams ap) {
         int msgType = ap.getFilterMessageType();
 
-        if (D) Log.d(TAG, "mmsSelected msgType: " + msgType);
+        if (D) {
+            Log.d(TAG, "mmsSelected msgType: " + msgType);
+        }
 
-        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
             return true;
+        }
 
-        if ((msgType & BluetoothMapAppParams.FILTER_NO_MMS) == 0)
+        if ((msgType & BluetoothMapAppParams.FILTER_NO_MMS) == 0) {
             return true;
+        }
 
         return false;
     }
@@ -1996,13 +2144,17 @@
     private boolean emailSelected(BluetoothMapAppParams ap) {
         int msgType = ap.getFilterMessageType();
 
-        if (D) Log.d(TAG, "emailSelected msgType: " + msgType);
+        if (D) {
+            Log.d(TAG, "emailSelected msgType: " + msgType);
+        }
 
-        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
             return true;
+        }
 
-        if ((msgType & BluetoothMapAppParams.FILTER_NO_EMAIL) == 0)
+        if ((msgType & BluetoothMapAppParams.FILTER_NO_EMAIL) == 0) {
             return true;
+        }
 
         return false;
     }
@@ -2017,53 +2169,64 @@
     private boolean imSelected(BluetoothMapAppParams ap) {
         int msgType = ap.getFilterMessageType();
 
-        if (D) Log.d(TAG, "imSelected msgType: " + msgType);
+        if (D) {
+            Log.d(TAG, "imSelected msgType: " + msgType);
+        }
 
-        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+        if (msgType == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
             return true;
+        }
 
-        if ((msgType & BluetoothMapAppParams.FILTER_NO_IM) == 0)
+        if ((msgType & BluetoothMapAppParams.FILTER_NO_IM) == 0) {
             return true;
+        }
 
         return false;
     }
 
     private void setFilterInfo(FilterInfo fi) {
         TelephonyManager tm =
-            (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         if (tm != null) {
             fi.mPhoneType = tm.getPhoneType();
             fi.mPhoneNum = tm.getLine1Number();
             fi.mPhoneAlphaTag = tm.getLine1AlphaTag();
-            if (D) Log.d(TAG, "phone type = " + fi.mPhoneType +
-                " phone num = " + fi.mPhoneNum +
-                " phone alpha tag = " + fi.mPhoneAlphaTag);
+            if (D) {
+                Log.d(TAG, "phone type = " + fi.mPhoneType + " phone num = " + fi.mPhoneNum
+                        + " phone alpha tag = " + fi.mPhoneAlphaTag);
+            }
         }
     }
 
     /**
      * Get a listing of message in folder after applying filter.
-     * @param folder Must contain a valid folder string != null
+     * @param folderElement Must contain a valid folder string != null
      * @param ap Parameters specifying message content and filters
      * @return Listing object containing requested messages
      */
     public BluetoothMapMessageListing msgListing(BluetoothMapFolderElement folderElement,
             BluetoothMapAppParams ap) {
-        if (D) Log.d(TAG, "msgListing: messageType = " + ap.getFilterMessageType() );
+        if (D) {
+            Log.d(TAG, "msgListing: messageType = " + ap.getFilterMessageType());
+        }
 
         BluetoothMapMessageListing bmList = new BluetoothMapMessageListing();
 
         /* We overwrite the parameter mask here if it is 0 or not present, as this
          * should cause all parameters to be included in the message list. */
-        if(ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
-                ap.getParameterMask() == 0) {
+        if (ap.getParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER
+                || ap.getParameterMask() == 0) {
             ap.setParameterMask(PARAMETER_MASK_ALL_ENABLED);
-            if (V) Log.v(TAG, "msgListing(): appParameterMask is zero or not present, " +
-                    "changing to all enabled by default: " + ap.getParameterMask());
+            if (V) {
+                Log.v(TAG, "msgListing(): appParameterMask is zero or not present, "
+                        + "changing to all enabled by default: " + ap.getParameterMask());
+            }
         }
-        if (V) Log.v(TAG, "folderElement hasSmsMmsContent = " + folderElement.hasSmsMmsContent() +
-                " folderElement.hasEmailContent = " + folderElement.hasEmailContent() +
-                " folderElement.hasImContent = " + folderElement.hasImContent());
+        if (V) {
+            Log.v(TAG, "folderElement hasSmsMmsContent = " + folderElement.hasSmsMmsContent()
+                    + " folderElement.hasEmailContent = " + folderElement.hasEmailContent()
+                    + " folderElement.hasImContent = " + folderElement.hasImContent());
+        }
 
         /* Cache some info used throughout filtering */
         FilterInfo fi = new FilterInfo();
@@ -2075,39 +2238,46 @@
         String limit = "";
         int countNum = ap.getMaxListCount();
         int offsetNum = ap.getStartOffset();
-        if(ap.getMaxListCount()>0){
-            limit=" LIMIT "+ (ap.getMaxListCount()+ap.getStartOffset());
+        if (ap.getMaxListCount() > 0) {
+            limit = " LIMIT " + (ap.getMaxListCount() + ap.getStartOffset());
         }
-        try{
+        try {
             if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
-                if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
-                                                 BluetoothMapAppParams.FILTER_NO_MMS|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM|
-                                                 BluetoothMapAppParams.FILTER_NO_IM)||
-                   ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
-                                                 BluetoothMapAppParams.FILTER_NO_MMS|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
-                                                 BluetoothMapAppParams.FILTER_NO_IM)){
+                if (ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL
+                        | BluetoothMapAppParams.FILTER_NO_MMS
+                        | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+                        | BluetoothMapAppParams.FILTER_NO_IM) || ap.getFilterMessageType() == (
+                        BluetoothMapAppParams.FILTER_NO_EMAIL | BluetoothMapAppParams.FILTER_NO_MMS
+                                | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+                                | BluetoothMapAppParams.FILTER_NO_IM)) {
                     //set real limit and offset if only this type is used
                     // (only if offset/limit is used)
-                    limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
-                    if(D) Log.d(TAG, "SMS Limit => "+limit);
+                    limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset();
+                    if (D) {
+                        Log.d(TAG, "SMS Limit => " + limit);
+                    }
                     offsetNum = 0;
                 }
                 fi.mMsgType = FilterInfo.TYPE_SMS;
-                if(ap.getFilterPriority() != 1){ /*SMS cannot have high priority*/
+                if (ap.getFilterPriority() != 1) { /*SMS cannot have high priority*/
                     String where = setWhereFilter(folderElement, fi, ap);
-                    if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
-                    smsCursor = mResolver.query(Sms.CONTENT_URI,
-                            SMS_PROJECTION, where, null, Sms.DATE + " DESC" + limit);
+                    if (D) {
+                        Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+                    }
+                    smsCursor = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+                            Sms.DATE + " DESC" + limit);
                     if (smsCursor != null) {
                         BluetoothMapMessageListingElement e = null;
                         // store column index so we dont have to look them up anymore (optimization)
-                        if(D) Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages.");
+                        if (D) {
+                            Log.d(TAG, "Found " + smsCursor.getCount() + " sms messages.");
+                        }
                         fi.setSmsColumns(smsCursor);
                         while (smsCursor.moveToNext()) {
                             if (matchAddresses(smsCursor, fi, ap)) {
-                                if(V) BluetoothMapUtils.printCursor(smsCursor);
+                                if (V) {
+                                    BluetoothMapUtils.printCursor(smsCursor);
+                                }
                                 e = element(smsCursor, fi, ap);
                                 bmList.add(e);
                             }
@@ -2117,31 +2287,39 @@
             }
 
             if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
-                if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM|
-                                                 BluetoothMapAppParams.FILTER_NO_IM)){
+                if (ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_EMAIL
+                        | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+                        | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+                        | BluetoothMapAppParams.FILTER_NO_IM)) {
                     //set real limit and offset if only this type is used
                     //(only if offset/limit is used)
-                    limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
-                    if(D) Log.d(TAG, "MMS Limit => "+limit);
+                    limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset();
+                    if (D) {
+                        Log.d(TAG, "MMS Limit => " + limit);
+                    }
                     offsetNum = 0;
                 }
                 fi.mMsgType = FilterInfo.TYPE_MMS;
                 String where = setWhereFilter(folderElement, fi, ap);
                 where += " AND " + INTERESTED_MESSAGE_TYPE_CLAUSE;
-                if(!where.isEmpty()) {
-                    if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
-                    mmsCursor = mResolver.query(Mms.CONTENT_URI,
-                            MMS_PROJECTION, where, null, Mms.DATE + " DESC" + limit);
+                if (!where.isEmpty()) {
+                    if (D) {
+                        Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+                    }
+                    mmsCursor = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
+                            Mms.DATE + " DESC" + limit);
                     if (mmsCursor != null) {
                         BluetoothMapMessageListingElement e = null;
                         // store column index so we dont have to look them up anymore (optimization)
                         fi.setMmsColumns(mmsCursor);
-                        if(D) Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages.");
+                        if (D) {
+                            Log.d(TAG, "Found " + mmsCursor.getCount() + " mms messages.");
+                        }
                         while (mmsCursor.moveToNext()) {
                             if (matchAddresses(mmsCursor, fi, ap)) {
-                                if(V) BluetoothMapUtils.printCursor(mmsCursor);
+                                if (V) {
+                                    BluetoothMapUtils.printCursor(mmsCursor);
+                                }
                                 e = element(mmsCursor, fi, ap);
                                 bmList.add(e);
                             }
@@ -2151,67 +2329,84 @@
             }
 
             if (emailSelected(ap) && folderElement.hasEmailContent()) {
-                if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM|
-                                                 BluetoothMapAppParams.FILTER_NO_IM)){
+                if (ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS
+                        | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+                        | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+                        | BluetoothMapAppParams.FILTER_NO_IM)) {
                     //set real limit and offset if only this type is used
                     //(only if offset/limit is used)
-                    limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
-                    if(D) Log.d(TAG, "Email Limit => "+limit);
+                    limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset();
+                    if (D) {
+                        Log.d(TAG, "Email Limit => " + limit);
+                    }
                     offsetNum = 0;
                 }
                 fi.mMsgType = FilterInfo.TYPE_EMAIL;
                 String where = setWhereFilter(folderElement, fi, ap);
 
-                if(!where.isEmpty()) {
-                    if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+                if (!where.isEmpty()) {
+                    if (D) {
+                        Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+                    }
                     Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-                    emailCursor = mResolver.query(contentUri,
-                            BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
-                            BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
+                    emailCursor =
+                            mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
+                                    where, null,
+                                    BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
                     if (emailCursor != null) {
                         BluetoothMapMessageListingElement e = null;
                         // store column index so we dont have to look them up anymore (optimization)
                         fi.setEmailMessageColumns(emailCursor);
                         int cnt = 0;
-                        if(D) Log.d(TAG, "Found " + emailCursor.getCount() + " email messages.");
+                        if (D) {
+                            Log.d(TAG, "Found " + emailCursor.getCount() + " email messages.");
+                        }
                         while (emailCursor.moveToNext()) {
-                            if(V) BluetoothMapUtils.printCursor(emailCursor);
+                            if (V) {
+                                BluetoothMapUtils.printCursor(emailCursor);
+                            }
                             e = element(emailCursor, fi, ap);
                             bmList.add(e);
                         }
-                    //   emailCursor.close();
+                        //   emailCursor.close();
                     }
                 }
             }
 
             if (imSelected(ap) && folderElement.hasImContent()) {
-                if(ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_CDMA|
-                                                 BluetoothMapAppParams.FILTER_NO_SMS_GSM|
-                                                 BluetoothMapAppParams.FILTER_NO_EMAIL)){
+                if (ap.getFilterMessageType() == (BluetoothMapAppParams.FILTER_NO_MMS
+                        | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+                        | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+                        | BluetoothMapAppParams.FILTER_NO_EMAIL)) {
                     //set real limit and offset if only this type is used
                     //(only if offset/limit is used)
-                    limit = " LIMIT " + ap.getMaxListCount() + " OFFSET "+ ap.getStartOffset();
-                    if(D) Log.d(TAG, "IM Limit => "+limit);
+                    limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset();
+                    if (D) {
+                        Log.d(TAG, "IM Limit => " + limit);
+                    }
                     offsetNum = 0;
                 }
                 fi.mMsgType = FilterInfo.TYPE_IM;
                 String where = setWhereFilter(folderElement, fi, ap);
-                if (D) Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+                if (D) {
+                    Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
+                }
 
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
                 imCursor = mResolver.query(contentUri,
-                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
-                        where, null, BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
+                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+                        BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
                 if (imCursor != null) {
                     BluetoothMapMessageListingElement e = null;
                     // store column index so we dont have to look them up anymore (optimization)
                     fi.setImMessageColumns(imCursor);
-                    if (D) Log.d(TAG, "Found " + imCursor.getCount() + " im messages.");
+                    if (D) {
+                        Log.d(TAG, "Found " + imCursor.getCount() + " im messages.");
+                    }
                     while (imCursor.moveToNext()) {
-                        if (V) BluetoothMapUtils.printCursor(imCursor);
+                        if (V) {
+                            BluetoothMapUtils.printCursor(imCursor);
+                        }
                         e = element(imCursor, fi, ap);
                         bmList.add(e);
                     }
@@ -2224,27 +2419,27 @@
             List<BluetoothMapMessageListingElement> list = bmList.getList();
             int listSize = list.size();
             Cursor tmpCursor = null;
-            for(int x=0;x<listSize;x++){
+            for (int x = 0; x < listSize; x++) {
                 BluetoothMapMessageListingElement ele = list.get(x);
                 /* If OBEX "GET" request header includes "ParameterMask" with 'Type' NOT set,
                  * then ele.getType() returns "null" even for a valid cursor.
                  * Avoid NullPointerException in equals() check when 'mType' value is "null" */
                 TYPE tmpType = ele.getType();
-                if (smsCursor!= null &&
-                        ((TYPE.SMS_GSM).equals(tmpType) || (TYPE.SMS_CDMA).equals(tmpType))) {
+                if (smsCursor != null && ((TYPE.SMS_GSM).equals(tmpType) || (TYPE.SMS_CDMA).equals(
+                        tmpType))) {
                     tmpCursor = smsCursor;
                     fi.mMsgType = FilterInfo.TYPE_SMS;
-                } else if(mmsCursor != null && (TYPE.MMS).equals(tmpType)) {
+                } else if (mmsCursor != null && (TYPE.MMS).equals(tmpType)) {
                     tmpCursor = mmsCursor;
                     fi.mMsgType = FilterInfo.TYPE_MMS;
-                } else if(emailCursor != null && ((TYPE.EMAIL).equals(tmpType))) {
+                } else if (emailCursor != null && ((TYPE.EMAIL).equals(tmpType))) {
                     tmpCursor = emailCursor;
                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
-                } else if(imCursor != null && ((TYPE.IM).equals(tmpType))) {
+                } else if (imCursor != null && ((TYPE.IM).equals(tmpType))) {
                     tmpCursor = imCursor;
                     fi.mMsgType = FilterInfo.TYPE_IM;
                 }
-                if(tmpCursor != null){
+                if (tmpCursor != null) {
                     tmpCursor.moveToPosition(ele.getCursorIndex());
                     setSenderAddressing(ele, tmpCursor, fi, ap);
                     setSenderName(ele, tmpCursor, fi, ap);
@@ -2259,7 +2454,7 @@
                     setReceptionStatus(ele, tmpCursor, fi, ap);
                     setAttachment(ele, tmpCursor, fi, ap);
 
-                    if(mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10 ){
+                    if (mMsgListingVersion > BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V10) {
                         setDeliveryStatus(ele, tmpCursor, fi, ap);
                         setThreadId(ele, tmpCursor, fi, ap);
                         setThreadName(ele, tmpCursor, fi, ap);
@@ -2268,26 +2463,37 @@
                 }
             }
         } finally {
-            if(emailCursor != null)emailCursor.close();
-            if(smsCursor != null)smsCursor.close();
-            if(mmsCursor != null)mmsCursor.close();
-            if(imCursor != null)imCursor.close();
+            if (emailCursor != null) {
+                emailCursor.close();
+            }
+            if (smsCursor != null) {
+                smsCursor.close();
+            }
+            if (mmsCursor != null) {
+                mmsCursor.close();
+            }
+            if (imCursor != null) {
+                imCursor.close();
+            }
         }
 
 
-        if(D)Log.d(TAG, "messagelisting end");
+        if (D) {
+            Log.d(TAG, "messagelisting end");
+        }
         return bmList;
     }
 
     /**
      * Get the size of the message listing
-     * @param folder Must contain a valid folder string != null
+     * @param folderElement Must contain a valid folder string != null
      * @param ap Parameters specifying message content and filters
      * @return Integer equal to message listing size
      */
-    public int msgListingSize(BluetoothMapFolderElement folderElement,
-            BluetoothMapAppParams ap) {
-        if (D) Log.d(TAG, "msgListingSize: folder = " + folderElement.getName());
+    public int msgListingSize(BluetoothMapFolderElement folderElement, BluetoothMapAppParams ap) {
+        if (D) {
+            Log.d(TAG, "msgListingSize: folder = " + folderElement.getName());
+        }
         int cnt = 0;
 
         /* Cache some info used throughout filtering */
@@ -2297,35 +2503,39 @@
         if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
             fi.mMsgType = FilterInfo.TYPE_SMS;
             String where = setWhereFilter(folderElement, fi, ap);
-            Cursor c = mResolver.query(Sms.CONTENT_URI,
-                    SMS_PROJECTION, where, null, Sms.DATE + " DESC");
+            Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+                    Sms.DATE + " DESC");
             try {
                 if (c != null) {
                     cnt = c.getCount();
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
         }
 
-        if (mmsSelected(ap)  && folderElement.hasSmsMmsContent()) {
+        if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
             fi.mMsgType = FilterInfo.TYPE_MMS;
             String where = setWhereFilter(folderElement, fi, ap);
-            Cursor c = mResolver.query(Mms.CONTENT_URI,
-                    MMS_PROJECTION, where, null, Mms.DATE + " DESC");
+            Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
+                    Mms.DATE + " DESC");
             try {
                 if (c != null) {
                     cnt += c.getCount();
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
         }
 
         if (emailSelected(ap) && folderElement.hasEmailContent()) {
             fi.mMsgType = FilterInfo.TYPE_EMAIL;
             String where = setWhereFilter(folderElement, fi, ap);
-            if(!where.isEmpty()) {
+            if (!where.isEmpty()) {
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
                 Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
                         where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
@@ -2334,7 +2544,9 @@
                         cnt += c.getCount();
                     }
                 } finally {
-                    if (c != null) c.close();
+                    if (c != null) {
+                        c.close();
+                    }
                 }
             }
         }
@@ -2342,69 +2554,79 @@
         if (imSelected(ap) && folderElement.hasImContent()) {
             fi.mMsgType = FilterInfo.TYPE_IM;
             String where = setWhereFilter(folderElement, fi, ap);
-            if(!where.isEmpty()) {
+            if (!where.isEmpty()) {
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
                 Cursor c = mResolver.query(contentUri,
-                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
-                        where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+                        BluetoothMapContract.MessageColumns.DATE + " DESC");
                 try {
                     if (c != null) {
                         cnt += c.getCount();
                     }
                 } finally {
-                    if (c != null) c.close();
+                    if (c != null) {
+                        c.close();
+                    }
                 }
             }
         }
 
-        if (D) Log.d(TAG, "msgListingSize: size = " + cnt);
+        if (D) {
+            Log.d(TAG, "msgListingSize: size = " + cnt);
+        }
         return cnt;
     }
 
     /**
      * Return true if there are unread messages in the requested list of messages
-     * @param folder folder where the message listing should come from
+     * @param folderElement folder where the message listing should come from
      * @param ap application parameter object
      * @return true if unread messages are in the list, else false
      */
     public boolean msgListingHasUnread(BluetoothMapFolderElement folderElement,
             BluetoothMapAppParams ap) {
-        if (D) Log.d(TAG, "msgListingHasUnread: folder = " + folderElement.getName());
+        if (D) {
+            Log.d(TAG, "msgListingHasUnread: folder = " + folderElement.getName());
+        }
         int cnt = 0;
 
         /* Cache some info used throughout filtering */
         FilterInfo fi = new FilterInfo();
         setFilterInfo(fi);
 
-       if (smsSelected(fi, ap)  && folderElement.hasSmsMmsContent()) {
+        if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
             fi.mMsgType = FilterInfo.TYPE_SMS;
             String where = setWhereFilterFolderType(folderElement, fi);
             where += " AND " + Sms.READ + "=0 ";
             where += setWhereFilterPeriod(ap, fi);
-            Cursor c = mResolver.query(Sms.CONTENT_URI,
-                SMS_PROJECTION, where, null, Sms.DATE + " DESC");
+            Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+                    Sms.DATE + " DESC");
             try {
                 if (c != null) {
                     cnt = c.getCount();
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
         }
 
-        if (mmsSelected(ap)  && folderElement.hasSmsMmsContent()) {
+        if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
             fi.mMsgType = FilterInfo.TYPE_MMS;
             String where = setWhereFilterFolderType(folderElement, fi);
             where += " AND " + Mms.READ + "=0 ";
             where += setWhereFilterPeriod(ap, fi);
-            Cursor c = mResolver.query(Mms.CONTENT_URI,
-                MMS_PROJECTION, where, null, Sms.DATE + " DESC");
+            Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
+                    Sms.DATE + " DESC");
             try {
                 if (c != null) {
                     cnt += c.getCount();
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
         }
 
@@ -2412,7 +2634,7 @@
         if (emailSelected(ap) && folderElement.getFolderId() != -1) {
             fi.mMsgType = FilterInfo.TYPE_EMAIL;
             String where = setWhereFilterFolderType(folderElement, fi);
-            if(!where.isEmpty()) {
+            if (!where.isEmpty()) {
                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
                 where += setWhereFilterPeriod(ap, fi);
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
@@ -2423,7 +2645,9 @@
                         cnt += c.getCount();
                     }
                 } finally {
-                    if (c != null) c.close();
+                    if (c != null) {
+                        c.close();
+                    }
                 }
             }
         }
@@ -2431,25 +2655,29 @@
         if (imSelected(ap) && folderElement.hasImContent()) {
             fi.mMsgType = FilterInfo.TYPE_IM;
             String where = setWhereFilter(folderElement, fi, ap);
-            if(!where.isEmpty()) {
+            if (!where.isEmpty()) {
                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
                 where += setWhereFilterPeriod(ap, fi);
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
                 Cursor c = mResolver.query(contentUri,
-                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION,
-                        where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+                        BluetoothMapContract.MessageColumns.DATE + " DESC");
                 try {
                     if (c != null) {
                         cnt += c.getCount();
                     }
                 } finally {
-                    if (c != null) c.close();
+                    if (c != null) {
+                        c.close();
+                    }
                 }
             }
         }
 
-        if (D) Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt);
-        return (cnt>0)?true:false;
+        if (D) {
+            Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt);
+        }
+        return cnt > 0;
     }
 
     /**
@@ -2460,16 +2688,20 @@
      */
     public BluetoothMapConvoListing convoListing(BluetoothMapAppParams ap, boolean sizeOnly) {
 
-        if (D) Log.d(TAG, "convoListing: " + " messageType = " + ap.getFilterMessageType() );
+        if (D) {
+            Log.d(TAG, "convoListing: " + " messageType = " + ap.getFilterMessageType());
+        }
         BluetoothMapConvoListing convoList = new BluetoothMapConvoListing();
 
         /* We overwrite the parameter mask here if it is 0 or not present, as this
          * should cause all parameters to be included in the message list. */
-        if(ap.getConvoParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
-                ap.getConvoParameterMask() == 0) {
+        if (ap.getConvoParameterMask() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER
+                || ap.getConvoParameterMask() == 0) {
             ap.setConvoParameterMask(CONVO_PARAMETER_MASK_DEFAULT);
-            if (D) Log.v(TAG, "convoListing(): appParameterMask is zero or not present, " +
-                    "changing to default: " + ap.getConvoParameterMask());
+            if (D) {
+                Log.v(TAG, "convoListing(): appParameterMask is zero or not present, "
+                        + "changing to default: " + ap.getConvoParameterMask());
+            }
         }
 
         /* Possible filters:
@@ -2494,34 +2726,35 @@
         Cursor smsMmsCursor = null;
         Cursor imEmailCursor = null;
         int offsetNum;
-        if(sizeOnly) {
+        if (sizeOnly) {
             offsetNum = 0;
         } else {
             offsetNum = ap.getStartOffset();
         }
         // Inverse meaning - hence a 1 is include.
-        int msgTypesInclude = ((~ap.getFilterMessageType())
-                & BluetoothMapAppParams.FILTER_MSG_TYPE_MASK);
-        int maxThreads = ap.getMaxListCount()+ap.getStartOffset();
+        int msgTypesInclude =
+                ((~ap.getFilterMessageType()) & BluetoothMapAppParams.FILTER_MSG_TYPE_MASK);
+        int maxThreads = ap.getMaxListCount() + ap.getStartOffset();
 
 
         try {
             if (smsSelected(fi, ap) || mmsSelected(ap)) {
                 String limit = "";
-                if((sizeOnly == false) && (ap.getMaxListCount()>0) &&
-                        (ap.getFilterRecipient()==null)){
+                if ((!sizeOnly) && (ap.getMaxListCount() > 0) && (ap.getFilterRecipient()
+                        == null)) {
                     /* We can only use limit if we do not have a contacts filter */
-                    limit=" LIMIT " + maxThreads;
+                    limit = " LIMIT " + maxThreads;
                 }
                 StringBuilder sortOrder = new StringBuilder(Threads.DATE + " DESC");
-                if((sizeOnly == false) &&
-                        ((msgTypesInclude & ~(BluetoothMapAppParams.FILTER_NO_SMS_GSM |
-                        BluetoothMapAppParams.FILTER_NO_SMS_CDMA) |
-                        BluetoothMapAppParams.FILTER_NO_MMS) == 0)
-                        && ap.getFilterRecipient() == null){
+                if ((!sizeOnly) && ((msgTypesInclude & ~(BluetoothMapAppParams.FILTER_NO_SMS_GSM
+                        | BluetoothMapAppParams.FILTER_NO_SMS_CDMA)
+                        | BluetoothMapAppParams.FILTER_NO_MMS) == 0)
+                        && ap.getFilterRecipient() == null) {
                     // SMS/MMS messages only and no recipient filter - use optimization.
-                    limit = " LIMIT " + ap.getMaxListCount()+" OFFSET "+ ap.getStartOffset();
-                    if(D) Log.d(TAG, "SMS Limit => "+limit);
+                    limit = " LIMIT " + ap.getMaxListCount() + " OFFSET " + ap.getStartOffset();
+                    if (D) {
+                        Log.d(TAG, "SMS Limit => " + limit);
+                    }
                     offsetNum = 0;
                 }
                 StringBuilder selection = new StringBuilder(120); // This covers most cases
@@ -2529,32 +2762,37 @@
                 selection.append("1=1 "); // just to simplify building the where-clause
                 setConvoWhereFilterSmsMms(selection, selectionArgs, fi, ap);
                 String[] args = null;
-                if(selectionArgs.size() > 0) {
+                if (selectionArgs.size() > 0) {
                     args = new String[selectionArgs.size()];
                     selectionArgs.toArray(args);
                 }
                 Uri uri = Threads.CONTENT_URI.buildUpon()
-                        .appendQueryParameter("simple", "true").build();
+                        .appendQueryParameter("simple", "true")
+                        .build();
                 sortOrder.append(limit);
-                if(D) Log.d(TAG, "Query using selection: " + selection.toString() +
-                        " - sortOrder: " + sortOrder.toString());
+                if (D) {
+                    Log.d(TAG, "Query using selection: " + selection.toString() + " - sortOrder: "
+                            + sortOrder.toString());
+                }
                 // TODO: Optimize: Reduce projection based on convo parameter mask
-                smsMmsCursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(),
-                        args, sortOrder.toString());
+                smsMmsCursor =
+                        mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(), args,
+                                sortOrder.toString());
                 if (smsMmsCursor != null) {
                     // store column index so we don't have to look them up anymore (optimization)
-                    if(D) Log.d(TAG, "Found " + smsMmsCursor.getCount()
-                            + " sms/mms conversations.");
+                    if (D) {
+                        Log.d(TAG, "Found " + smsMmsCursor.getCount() + " sms/mms conversations.");
+                    }
                     BluetoothMapConvoListingElement convoElement = null;
                     smsMmsCursor.moveToPosition(-1);
-                    if(ap.getFilterRecipient() == null) {
+                    if (ap.getFilterRecipient() == null) {
                         int count = 0;
                         // We have no Recipient filter, add contacts after the list is reduced
                         while (smsMmsCursor.moveToNext()) {
                             convoElement = createConvoElement(smsMmsCursor, fi, ap);
                             convoList.add(convoElement);
                             count++;
-                            if(sizeOnly == false && count >= maxThreads) {
+                            if (!sizeOnly && count >= maxThreads) {
                                 break;
                             }
                         }
@@ -2570,10 +2808,10 @@
                             // the filter, hence the item is irrelevant
                             // TODO: Perhaps the spec. should be changes to be able to search on
                             //       phone number as well?
-                            if(addSmsMmsContacts(convoElement, contacts, idsStr,
+                            if (addSmsMmsContacts(convoElement, contacts, idsStr,
                                     ap.getFilterRecipient(), ap)) {
                                 convoList.add(convoElement);
-                                if(sizeOnly == false && count >= maxThreads) {
+                                if (!sizeOnly && count >= maxThreads) {
                                     break;
                                 }
                             }
@@ -2584,55 +2822,66 @@
 
             if (emailSelected(ap) || imSelected(ap)) {
                 int count = 0;
-                if(emailSelected(ap)) {
+                if (emailSelected(ap)) {
                     fi.mMsgType = FilterInfo.TYPE_EMAIL;
-                } else if(imSelected(ap)) {
+                } else if (imSelected(ap)) {
                     fi.mMsgType = FilterInfo.TYPE_IM;
                 }
-                if (D) Log.d(TAG, "msgType: " + fi.mMsgType);
+                if (D) {
+                    Log.d(TAG, "msgType: " + fi.mMsgType);
+                }
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
 
                 contentUri = appendConvoListQueryParameters(ap, contentUri);
-                if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
+                if (V) {
+                    Log.v(TAG, "URI with parameters: " + contentUri.toString());
+                }
                 // TODO: Optimize: Reduce projection based on convo parameter mask
-                imEmailCursor = mResolver.query(contentUri,
-                        BluetoothMapContract.BT_CONVERSATION_PROJECTION,
-                        null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
-                        + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
-                        + " ASC");
+                imEmailCursor =
+                        mResolver.query(contentUri, BluetoothMapContract.BT_CONVERSATION_PROJECTION,
+                                null, null,
+                                BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+                                        + " DESC, "
+                                        + BluetoothMapContract.ConversationColumns.THREAD_ID
+                                        + " ASC");
                 if (imEmailCursor != null) {
                     BluetoothMapConvoListingElement e = null;
                     // store column index so we don't have to look them up anymore (optimization)
                     // Here we rely on only a single account-based message type for each MAS.
                     fi.setEmailImConvoColumns(imEmailCursor);
                     boolean isValid = imEmailCursor.moveToNext();
-                    if(D) Log.d(TAG, "Found " + imEmailCursor.getCount()
-                            + " EMAIL/IM conversations. isValid = " + isValid);
-                    while (isValid && ((sizeOnly == true) || (count < maxThreads))) {
+                    if (D) {
+                        Log.d(TAG, "Found " + imEmailCursor.getCount()
+                                + " EMAIL/IM conversations. isValid = " + isValid);
+                    }
+                    while (isValid && ((sizeOnly) || (count < maxThreads))) {
                         long threadId = imEmailCursor.getLong(fi.mConvoColConvoId);
                         long nextThreadId;
-                        count ++;
+                        count++;
                         e = createConvoElement(imEmailCursor, fi, ap);
                         convoList.add(e);
 
                         do {
                             nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
-                            if(V) Log.i(TAG, "  threadId = " + threadId + " newThreadId = " +
-                                    nextThreadId);
+                            if (V) {
+                                Log.i(TAG, "  threadId = " + threadId + " newThreadId = "
+                                        + nextThreadId);
+                            }
                             // TODO: This seems rather inefficient in the case where we do not need
                             //       to reduce the list.
-                        } while ((nextThreadId == threadId) &&
-                                (isValid = imEmailCursor.moveToNext() == true));
+                        } while ((nextThreadId == threadId) && (isValid =
+                                imEmailCursor.moveToNext()));
                     }
                 }
             }
 
-            if(D) Log.d(TAG, "Done adding conversations - list size:" +
-                    convoList.getCount());
+            if (D) {
+                Log.d(TAG, "Done adding conversations - list size:" + convoList.getCount());
+            }
 
             // If sizeOnly - we are all done here - return the list as is - no need to populate the
             // list.
-            if(sizeOnly) {
+            if (sizeOnly) {
                 return convoList;
             }
 
@@ -2642,50 +2891,64 @@
             convoList.segment(ap.getMaxListCount(), offsetNum);
             List<BluetoothMapConvoListingElement> list = convoList.getList();
             int listSize = list.size();
-            if(V) Log.i(TAG, "List Size:" + listSize);
+            if (V) {
+                Log.i(TAG, "List Size:" + listSize);
+            }
             Cursor tmpCursor = null;
             SmsMmsContacts contacts = new SmsMmsContacts();
-            for(int x=0;x<listSize;x++){
+            for (int x = 0; x < listSize; x++) {
                 BluetoothMapConvoListingElement ele = list.get(x);
                 TYPE type = ele.getType();
-                switch(type) {
-                case SMS_CDMA:
-                case SMS_GSM:
-                case MMS: {
-                    tmpCursor = null; // SMS/MMS needs special treatment
-                    if(smsMmsCursor != null) {
-                        populateSmsMmsConvoElement(ele, smsMmsCursor, ap, contacts);
+                switch (type) {
+                    case SMS_CDMA:
+                    case SMS_GSM:
+                    case MMS: {
+                        tmpCursor = null; // SMS/MMS needs special treatment
+                        if (smsMmsCursor != null) {
+                            populateSmsMmsConvoElement(ele, smsMmsCursor, ap, contacts);
+                        }
+                        if (D) {
+                            fi.mMsgType = FilterInfo.TYPE_IM;
+                        }
+                        break;
                     }
-                    if(D) fi.mMsgType = FilterInfo.TYPE_IM;
-                    break;
-                }
-                case EMAIL:
-                    tmpCursor = imEmailCursor;
-                    fi.mMsgType = FilterInfo.TYPE_EMAIL;
-                    break;
-                case IM:
-                    tmpCursor = imEmailCursor;
-                    fi.mMsgType = FilterInfo.TYPE_IM;
-                    break;
-                default:
-                    tmpCursor = null;
-                    break;
+                    case EMAIL:
+                        tmpCursor = imEmailCursor;
+                        fi.mMsgType = FilterInfo.TYPE_EMAIL;
+                        break;
+                    case IM:
+                        tmpCursor = imEmailCursor;
+                        fi.mMsgType = FilterInfo.TYPE_IM;
+                        break;
+                    default:
+                        tmpCursor = null;
+                        break;
                 }
 
-                if(D) Log.d(TAG, "Working on cursor of type " + fi.mMsgType);
+                if (D) {
+                    Log.d(TAG, "Working on cursor of type " + fi.mMsgType);
+                }
 
-                if(tmpCursor != null){
+                if (tmpCursor != null) {
                     populateImEmailConvoElement(ele, tmpCursor, ap, fi);
-                }else {
+                } else {
                     // No, it will be for SMS/MMS at the moment
-                    if(D) Log.d(TAG, "tmpCursor is Null - something is wrong - or the message is" +
-                            " of type SMS/MMS");
+                    if (D) {
+                        Log.d(TAG, "tmpCursor is Null - something is wrong - or the message is"
+                                + " of type SMS/MMS");
+                    }
                 }
             }
         } finally {
-            if(imEmailCursor != null)imEmailCursor.close();
-            if(smsMmsCursor != null)smsMmsCursor.close();
-            if(D)Log.d(TAG, "conversation end");
+            if (imEmailCursor != null) {
+                imEmailCursor.close();
+            }
+            if (smsMmsCursor != null) {
+                smsMmsCursor.close();
+            }
+            if (D) {
+                Log.d(TAG, "conversation end");
+            }
         }
         return convoList;
     }
@@ -2700,78 +2963,78 @@
     boolean refreshSmsMmsConvoVersions() {
         boolean listChangeDetected = false;
         Cursor cursor = null;
-        Uri uri = Threads.CONTENT_URI.buildUpon()
-                .appendQueryParameter("simple", "true").build();
-        cursor = mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, null,
-                null, Threads.DATE + " DESC");
+        Uri uri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
+        cursor =
+                mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, null, null, Threads.DATE + " DESC");
         try {
             if (cursor != null) {
                 // store column index so we don't have to look them up anymore (optimization)
-                if(D) Log.d(TAG, "Found " + cursor.getCount()
-                        + " sms/mms conversations.");
+                if (D) {
+                    Log.d(TAG, "Found " + cursor.getCount() + " sms/mms conversations.");
+                }
                 BluetoothMapConvoListingElement convoElement = null;
                 cursor.moveToPosition(-1);
                 synchronized (getSmsMmsConvoList()) {
                     int size = Math.max(getSmsMmsConvoList().size(), cursor.getCount());
-                    HashMap<Long,BluetoothMapConvoListingElement> newList =
-                            new HashMap<Long,BluetoothMapConvoListingElement>(size);
+                    HashMap<Long, BluetoothMapConvoListingElement> newList =
+                            new HashMap<Long, BluetoothMapConvoListingElement>(size);
                     while (cursor.moveToNext()) {
                         // TODO: Extract to function, that can be called at listing, which returns
                         //       the versionCounter(existing or new).
                         boolean convoChanged = false;
                         Long id = cursor.getLong(MMS_SMS_THREAD_COL_ID);
                         convoElement = getSmsMmsConvoList().remove(id);
-                        if(convoElement == null) {
+                        if (convoElement == null) {
                             // New conversation added
                             convoElement = new BluetoothMapConvoListingElement();
                             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS, id);
                             listChangeDetected = true;
                             convoElement.setVersionCounter(0);
                         }
-                        // Currently we only need to compare name, last_activity and read_status, and
+                        // Currently we only need to compare name, lastActivity and read_status, and
                         // name is not used for SMS/MMS.
                         // msg delete will be handled by update folderVersionCounter().
-                        long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
-                        boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
-                                true : false;
+                        long lastActivity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
+                        boolean read = cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1;
 
-                        if(last_activity != convoElement.getLastActivity()) {
+                        if (lastActivity != convoElement.getLastActivity()) {
                             convoChanged = true;
-                            convoElement.setLastActivity(last_activity);
+                            convoElement.setLastActivity(lastActivity);
                         }
 
-                        if(read != convoElement.getReadBool()) {
+                        if (read != convoElement.getReadBool()) {
                             convoChanged = true;
                             convoElement.setRead(read, false);
                         }
 
                         String idsStr = cursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
-                        if(!idsStr.equals(convoElement.getSmsMmsContacts())) {
-                            // This should not trigger a change in conversationVersionCounter only the
+                        if (!idsStr.equals(convoElement.getSmsMmsContacts())) {
+                            // This should not trigger a change in conversationVersionCounter
+                            // only the
                             // ConvoListVersionCounter.
                             listChangeDetected = true;
                             convoElement.setSmsMmsContacts(idsStr);
                         }
 
-                        if(convoChanged) {
+                        if (convoChanged) {
                             listChangeDetected = true;
                             convoElement.incrementVersionCounter();
                         }
                         newList.put(id, convoElement);
                     }
                     // If we still have items on the old list, something was deleted
-                    if(getSmsMmsConvoList().size() != 0) {
+                    if (getSmsMmsConvoList().size() != 0) {
                         listChangeDetected = true;
                     }
                     setSmsMmsConvoList(newList);
                 }
 
-                if(listChangeDetected) {
+                if (listChangeDetected) {
                     mMasInstance.updateSmsMmsConvoListVersionCounter();
                 }
             }
         } finally {
-            if(cursor != null) {
+            if (cursor != null) {
                 cursor.close();
             }
         }
@@ -2790,12 +3053,12 @@
 
         Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVERSATION);
 
-        if(V) Log.v(TAG, "URI with parameters: " + contentUri.toString());
-        Cursor imEmailCursor = mResolver.query(contentUri,
-                CONVO_VERSION_PROJECTION,
-                null, null, BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
-                + " DESC, " + BluetoothMapContract.ConversationColumns.THREAD_ID
-                + " ASC");
+        if (V) {
+            Log.v(TAG, "URI with parameters: " + contentUri.toString());
+        }
+        Cursor imEmailCursor = mResolver.query(contentUri, CONVO_VERSION_PROJECTION, null, null,
+                BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY + " DESC, "
+                        + BluetoothMapContract.ConversationColumns.THREAD_ID + " ASC");
         try {
             if (imEmailCursor != null) {
                 BluetoothMapConvoListingElement convoElement = null;
@@ -2803,18 +3066,20 @@
                 // Here we rely on only a single account-based message type for each MAS.
                 fi.setEmailImConvoColumns(imEmailCursor);
                 boolean isValid = imEmailCursor.moveToNext();
-                if(V) Log.d(TAG, "Found " + imEmailCursor.getCount()
-                        + " EMAIL/IM conversations. isValid = " + isValid);
+                if (V) {
+                    Log.d(TAG, "Found " + imEmailCursor.getCount()
+                            + " EMAIL/IM conversations. isValid = " + isValid);
+                }
                 synchronized (getImEmailConvoList()) {
                     int size = Math.max(getImEmailConvoList().size(), imEmailCursor.getCount());
                     boolean convoChanged = false;
-                    HashMap<Long,BluetoothMapConvoListingElement> newList =
-                            new HashMap<Long,BluetoothMapConvoListingElement>(size);
+                    HashMap<Long, BluetoothMapConvoListingElement> newList =
+                            new HashMap<Long, BluetoothMapConvoListingElement>(size);
                     while (isValid) {
                         long id = imEmailCursor.getLong(fi.mConvoColConvoId);
                         long nextThreadId;
                         convoElement = getImEmailConvoList().remove(id);
-                        if(convoElement == null) {
+                        if (convoElement == null) {
                             // New conversation added
                             convoElement = new BluetoothMapConvoListingElement();
                             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
@@ -2823,57 +3088,57 @@
                         }
                         String name = imEmailCursor.getString(fi.mConvoColName);
                         String summary = imEmailCursor.getString(fi.mConvoColSummary);
-                        long last_activity = imEmailCursor.getLong(fi.mConvoColLastActivity);
-                        boolean read = (imEmailCursor.getInt(fi.mConvoColRead) == 1) ?
-                                true : false;
+                        long lastActivity = imEmailCursor.getLong(fi.mConvoColLastActivity);
+                        boolean read = imEmailCursor.getInt(fi.mConvoColRead) == 1;
 
-                        if(last_activity != convoElement.getLastActivity()) {
+                        if (lastActivity != convoElement.getLastActivity()) {
                             convoChanged = true;
-                            convoElement.setLastActivity(last_activity);
+                            convoElement.setLastActivity(lastActivity);
                         }
 
-                        if(read != convoElement.getReadBool()) {
+                        if (read != convoElement.getReadBool()) {
                             convoChanged = true;
                             convoElement.setRead(read, false);
                         }
 
-                        if(name != null && !name.equals(convoElement.getName())) {
+                        if (name != null && !name.equals(convoElement.getName())) {
                             convoChanged = true;
                             convoElement.setName(name);
                         }
 
-                        if(summary != null && !summary.equals(convoElement.getFullSummary())) {
+                        if (summary != null && !summary.equals(convoElement.getFullSummary())) {
                             convoChanged = true;
                             convoElement.setSummary(summary);
                         }
-                        /* If the query returned one row for each contact, skip all the dublicates */
+                        /* If the query returned one row for each contact, skip all the
+                        dublicates */
                         do {
                             nextThreadId = imEmailCursor.getLong(fi.mConvoColConvoId);
-                            if(V) Log.i(TAG, "  threadId = " + id + " newThreadId = " +
-                                    nextThreadId);
-                        } while ((nextThreadId == id) &&
-                                (isValid = imEmailCursor.moveToNext() == true));
+                            if (V) {
+                                Log.i(TAG, "  threadId = " + id + " newThreadId = " + nextThreadId);
+                            }
+                        } while ((nextThreadId == id) && (isValid = imEmailCursor.moveToNext()));
 
-                        if(convoChanged) {
+                        if (convoChanged) {
                             listChangeDetected = true;
                             convoElement.incrementVersionCounter();
                         }
                         newList.put(id, convoElement);
                     }
                     // If we still have items on the old list, something was deleted
-                    if(getImEmailConvoList().size() != 0) {
+                    if (getImEmailConvoList().size() != 0) {
                         listChangeDetected = true;
                     }
                     setImEmailConvoList(newList);
                 }
             }
         } finally {
-            if(imEmailCursor != null) {
+            if (imEmailCursor != null) {
                 imEmailCursor.close();
             }
         }
 
-        if(listChangeDetected) {
+        if (listChangeDetected) {
             mMasInstance.updateImEmailConvoListVersionCounter();
         }
         return listChangeDetected;
@@ -2891,7 +3156,7 @@
         BluetoothMapConvoListingElement convoElement = getSmsMmsConvoList().get(id);
         boolean listChangeDetected = false;
         boolean convoChanged = false;
-        if(convoElement == null) {
+        if (convoElement == null) {
             // New conversation added
             convoElement = new BluetoothMapConvoListingElement();
             getSmsMmsConvoList().put(id, convoElement);
@@ -2899,25 +3164,24 @@
             listChangeDetected = true;
             convoElement.setVersionCounter(0);
         }
-        long last_activity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
-        boolean read = (cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
-                true : false;
+        long lastActivity = cursor.getLong(MMS_SMS_THREAD_COL_DATE);
+        boolean read = cursor.getInt(MMS_SMS_THREAD_COL_READ) == 1;
 
-        if(last_activity != convoElement.getLastActivity()) {
+        if (lastActivity != convoElement.getLastActivity()) {
             convoChanged = true;
-            convoElement.setLastActivity(last_activity);
+            convoElement.setLastActivity(lastActivity);
         }
 
-        if(read != convoElement.getReadBool()) {
+        if (read != convoElement.getReadBool()) {
             convoChanged = true;
             convoElement.setRead(read, false);
         }
 
-        if(convoChanged) {
+        if (convoChanged) {
             listChangeDetected = true;
             convoElement.incrementVersionCounter();
         }
-        if(listChangeDetected) {
+        if (listChangeDetected) {
             mMasInstance.updateSmsMmsConvoListVersionCounter();
         }
         ele.setVersionCounter(convoElement.getVersionCounter());
@@ -2936,9 +3200,11 @@
         BluetoothMapConvoListingElement convoElement = getImEmailConvoList().get(id);
         boolean listChangeDetected = false;
         boolean convoChanged = false;
-        if(convoElement == null) {
+        if (convoElement == null) {
             // New conversation added
-            if(V) Log.d(TAG, "Added new conversation with ID = " + id);
+            if (V) {
+                Log.d(TAG, "Added new conversation with ID = " + id);
+            }
             convoElement = new BluetoothMapConvoListingElement();
             convoElement.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, id);
             getImEmailConvoList().put(id, convoElement);
@@ -2946,31 +3212,32 @@
             convoElement.setVersionCounter(0);
         }
         String name = cursor.getString(fi.mConvoColName);
-        long last_activity = cursor.getLong(fi.mConvoColLastActivity);
-        boolean read = (cursor.getInt(fi.mConvoColRead) == 1) ?
-                true : false;
+        long lastActivity = cursor.getLong(fi.mConvoColLastActivity);
+        boolean read = cursor.getInt(fi.mConvoColRead) == 1;
 
-        if(last_activity != convoElement.getLastActivity()) {
+        if (lastActivity != convoElement.getLastActivity()) {
             convoChanged = true;
-            convoElement.setLastActivity(last_activity);
+            convoElement.setLastActivity(lastActivity);
         }
 
-        if(read != convoElement.getReadBool()) {
+        if (read != convoElement.getReadBool()) {
             convoChanged = true;
             convoElement.setRead(read, false);
         }
 
-        if(name != null && !name.equals(convoElement.getName())) {
+        if (name != null && !name.equals(convoElement.getName())) {
             convoChanged = true;
             convoElement.setName(name);
         }
 
-        if(convoChanged) {
+        if (convoChanged) {
             listChangeDetected = true;
-            if(V) Log.d(TAG, "conversation with ID = " + id + " changed");
+            if (V) {
+                Log.d(TAG, "conversation with ID = " + id + " changed");
+            }
             convoElement.incrementVersionCounter();
         }
-        if(listChangeDetected) {
+        if (listChangeDetected) {
             mMasInstance.updateImEmailConvoListVersionCounter();
         }
         ele.setVersionCounter(convoElement.getVersionCounter());
@@ -2983,8 +3250,7 @@
      * @param contacts
      */
     private void populateSmsMmsConvoElement(BluetoothMapConvoListingElement ele,
-            Cursor smsMmsCursor, BluetoothMapAppParams ap,
-            SmsMmsContacts contacts) {
+            Cursor smsMmsCursor, BluetoothMapAppParams ap, SmsMmsContacts contacts) {
         smsMmsCursor.moveToPosition(ele.getCursorIndex());
         // TODO: If we ever get beyond 31 bit, change to long
         int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
@@ -2994,15 +3260,14 @@
         ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_SMS_MMS,
                 smsMmsCursor.getLong(MMS_SMS_THREAD_COL_ID));
 
-        boolean read = (smsMmsCursor.getInt(MMS_SMS_THREAD_COL_READ) == 1) ?
-                true : false;
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
+        boolean read = smsMmsCursor.getInt(MMS_SMS_THREAD_COL_READ) == 1;
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
             ele.setRead(read, true);
         } else {
             ele.setRead(read, false);
         }
 
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
             long timeStamp = smsMmsCursor.getLong(MMS_SMS_THREAD_COL_DATE);
             ele.setLastActivity(timeStamp);
         } else {
@@ -3010,31 +3275,32 @@
             ele.setLastActivity(-1);
         }
 
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
             updateSmsMmsConvoVersion(smsMmsCursor, ele);
         }
 
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
             ele.setName(""); // We never have a thread name for SMS/MMS
         }
 
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
             String summary = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET);
             String cs = smsMmsCursor.getString(MMS_SMS_THREAD_COL_SNIPPET_CS);
-            if(summary != null && cs != null && !cs.equals("UTF-8")) {
+            if (summary != null && cs != null && !cs.equals("UTF-8")) {
                 try {
                     // TODO: Not sure this is how to convert to UTF-8
-                    summary = new String(summary.getBytes(cs),"UTF-8");
-                } catch (UnsupportedEncodingException e){/*Cannot happen*/}
+                    summary = new String(summary.getBytes(cs), "UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    Log.e(TAG, "populateSmsMmsConvoElement: " + e);
+                }
             }
             ele.setSummary(summary);
         }
 
-        if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
-            if(ap.getFilterRecipient() == null) {
+        if ((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
+            if (ap.getFilterRecipient() == null) {
                 // Add contacts only if not already added
-                String idsStr =
-                        smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
+                String idsStr = smsMmsCursor.getString(MMS_SMS_THREAD_COL_RECIPIENT_IDS);
                 addSmsMmsContacts(ele, contacts, idsStr, null, ap);
             }
         }
@@ -3045,8 +3311,8 @@
      * @param tmpCursor
      * @param fi
      */
-    private void populateImEmailConvoElement( BluetoothMapConvoListingElement ele,
-            Cursor tmpCursor, BluetoothMapAppParams ap, FilterInfo fi) {
+    private void populateImEmailConvoElement(BluetoothMapConvoListingElement ele, Cursor tmpCursor,
+            BluetoothMapAppParams ap, FilterInfo fi) {
         tmpCursor.moveToPosition(ele.getCursorIndex());
         // TODO: If we ever get beyond 31 bit, change to long
         int parameterMask = (int) ap.getConvoParameterMask(); // We always set a default value
@@ -3055,18 +3321,18 @@
         // Mandatory field
         ele.setConvoId(BluetoothMapUtils.CONVO_ID_TYPE_EMAIL_IM, threadId);
 
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_NAME) != 0) {
             ele.setName(tmpCursor.getString(fi.mConvoColName));
         }
 
         boolean reportRead = false;
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_READ_STATUS) != 0) {
             reportRead = true;
         }
-        ele.setRead(((1==tmpCursor.getInt(fi.mConvoColRead))?true:false), reportRead);
+        ele.setRead((1 == tmpCursor.getInt(fi.mConvoColRead)), reportRead);
 
         long timestamp = tmpCursor.getLong(fi.mConvoColLastActivity);
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY) != 0) {
             ele.setLastActivity(timestamp);
         } else {
             // We need to delete the time stamp, if it was added for multi msg-type
@@ -3074,47 +3340,46 @@
         }
 
 
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_VERSION_COUNTER) != 0) {
             updateImEmailConvoVersion(tmpCursor, fi, ele);
         }
-        if((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_CONVO_SUMMARY) != 0) {
             ele.setSummary(tmpCursor.getString(fi.mConvoColSummary));
         }
         // TODO: For optimization, we could avoid joining the contact and convo tables
         //       if we have no filter nor this bit is set.
-        if((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
+        if ((parameterMask & CONVO_PARAM_MASK_PARTTICIPANTS) != 0) {
             do {
                 BluetoothMapConvoContactElement c = new BluetoothMapConvoContactElement();
-                if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) != 0) {
-                    c.setBtUid(new SignedLongLong(tmpCursor.getLong(fi.mContactColBtUid),0));
+                if ((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) != 0) {
+                    c.setBtUid(new SignedLongLong(tmpCursor.getLong(fi.mContactColBtUid), 0));
                 }
-                if((parameterMask & CONVO_PARAM_MASK_PART_CHAT_STATE) != 0) {
+                if ((parameterMask & CONVO_PARAM_MASK_PART_CHAT_STATE) != 0) {
                     c.setChatState(tmpCursor.getInt(fi.mContactColChatState));
                 }
-                if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE) != 0) {
+                if ((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE) != 0) {
                     c.setPresenceAvailability(tmpCursor.getInt(fi.mContactColPresenceState));
                 }
-                if((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE_TEXT) != 0) {
+                if ((parameterMask & CONVO_PARAM_MASK_PART_PRESENCE_TEXT) != 0) {
                     c.setPresenceStatus(tmpCursor.getString(fi.mContactColPresenceText));
                 }
-                if((parameterMask & CONVO_PARAM_MASK_PART_PRIORITY) != 0) {
+                if ((parameterMask & CONVO_PARAM_MASK_PART_PRIORITY) != 0) {
                     c.setPriority(tmpCursor.getInt(fi.mContactColPriority));
                 }
-                if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) != 0) {
+                if ((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) != 0) {
                     c.setDisplayName(tmpCursor.getString(fi.mContactColNickname));
                 }
-                if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
+                if ((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
                     c.setContactId(tmpCursor.getString(fi.mContactColContactUci));
                 }
-                if((parameterMask & CONVO_PARAM_MASK_PART_LAST_ACTIVITY) != 0) {
+                if ((parameterMask & CONVO_PARAM_MASK_PART_LAST_ACTIVITY) != 0) {
                     c.setLastActivity(tmpCursor.getLong(fi.mContactColLastActive));
                 }
-                if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
+                if ((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
                     c.setName(tmpCursor.getString(fi.mContactColName));
                 }
                 ele.addContact(c);
-            } while (tmpCursor.moveToNext() == true
-                    && tmpCursor.getLong(fi.mConvoColConvoId) == threadId);
+            } while (tmpCursor.moveToNext() && tmpCursor.getLong(fi.mConvoColConvoId) == threadId);
         }
     }
 
@@ -3125,45 +3390,42 @@
      * @param contentUri the URI to append parameters to
      * @return the new URI with the appended parameters (if any)
      */
-    private Uri appendConvoListQueryParameters(BluetoothMapAppParams ap,
-            Uri contentUri) {
+    private Uri appendConvoListQueryParameters(BluetoothMapAppParams ap, Uri contentUri) {
         Builder newUri = contentUri.buildUpon();
         String str = ap.getFilterRecipient();
-        if(str != null) {
+        if (str != null) {
             str = str.trim();
             str = str.replace("*", "%");
             newUri.appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING, str);
         }
         long time = ap.getFilterLastActivityBegin();
-        if(time > 0) {
+        if (time > 0) {
             newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_BEGIN,
                     Long.toString(time));
         }
         time = ap.getFilterLastActivityEnd();
-        if(time > 0) {
+        if (time > 0) {
             newUri.appendQueryParameter(BluetoothMapContract.FILTER_PERIOD_END,
                     Long.toString(time));
         }
         int readStatus = ap.getFilterReadStatus();
-        if(readStatus > 0) {
-            if(readStatus == 1) {
+        if (readStatus > 0) {
+            if (readStatus == 1) {
                 // Conversations with Unread messages only
-                newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
-                        "false");
-            }else if(readStatus == 2) {
+                newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS, "false");
+            } else if (readStatus == 2) {
                 // Conversations with all read messages only
-                newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS,
-                        "true");
+                newUri.appendQueryParameter(BluetoothMapContract.FILTER_READ_STATUS, "true");
             }
             // if both are set it will be the same as requesting an empty list, but
             // as it makes no sense with such a structure in a bit mask, we treat
             // requesting both the same as no filtering.
         }
         long convoId = -1;
-        if(ap.getFilterConvoId() != null) {
+        if (ap.getFilterConvoId() != null) {
             convoId = ap.getFilterConvoId().getLeastSignificantBits();
         }
-        if(convoId > 0) {
+        if (convoId > 0) {
             newUri.appendQueryParameter(BluetoothMapContract.FILTER_THREAD_ID,
                     Long.toString(convoId));
         }
@@ -3184,7 +3446,7 @@
      * @param recipientFilter
      * @return
      */
-    private boolean addSmsMmsContacts( BluetoothMapConvoListingElement convoElement,
+    private boolean addSmsMmsContacts(BluetoothMapConvoListingElement convoElement,
             SmsMmsContacts contacts, String idsStr, String recipientFilter,
             BluetoothMapAppParams ap) {
         BluetoothMapConvoContactElement contactElement;
@@ -3192,7 +3454,7 @@
         boolean foundContact = false;
         String[] ids = idsStr.split(" ");
         long[] longIds = new long[ids.length];
-        if(recipientFilter != null) {
+        if (recipientFilter != null) {
             recipientFilter = recipientFilter.trim();
         }
 
@@ -3201,27 +3463,27 @@
             try {
                 longId = Long.parseLong(ids[i]);
                 longIds[i] = longId;
-                if(recipientFilter == null) {
+                if (recipientFilter == null) {
                     // If there is not filter, all we need to do is to parse the ids
                     foundContact = true;
                     continue;
                 }
                 String addr = contacts.getPhoneNumber(mResolver, longId);
-                if(addr == null) {
+                if (addr == null) {
                     // This can only happen if all messages from a contact is deleted while
                     // performing the query.
                     continue;
                 }
                 MapContact contact =
                         contacts.getContactNameFromPhone(addr, mResolver, recipientFilter);
-                if(D) {
+                if (D) {
                     Log.d(TAG, "  id " + longId + ": " + addr);
-                    if(contact != null) {
-                        Log.d(TAG,"  contact name: " + contact.getName() + "  X-BT-UID: "
-                                + contact.getXBtUid());
+                    if (contact != null) {
+                        Log.d(TAG, "  contact name: " + contact.getName() + "  X-BT-UID: " + contact
+                                .getXBtUid());
                     }
                 }
-                if(contact == null) {
+                if (contact == null) {
                     continue;
                 }
                 foundContact = true;
@@ -3231,11 +3493,11 @@
             }
         }
 
-        if(foundContact == true) {
+        if (foundContact) {
             foundContact = false;
             for (long id : longIds) {
                 String addr = contacts.getPhoneNumber(mResolver, id);
-                if(addr == null) {
+                if (addr == null) {
                     // This can only happen if all messages from a contact is deleted while
                     // performing the query.
                     continue;
@@ -3243,26 +3505,26 @@
                 foundContact = true;
                 MapContact contact = contacts.getContactNameFromPhone(addr, mResolver);
 
-                if(contact == null) {
+                if (contact == null) {
                     // We do not have a contact, we need to manually add one
                     contactElement = new BluetoothMapConvoContactElement();
-                    if((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
+                    if ((parameterMask & CONVO_PARAM_MASK_PART_NAME) != 0) {
                         contactElement.setName(addr); // Use the phone number as name
                     }
-                    if((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
+                    if ((parameterMask & CONVO_PARAM_MASK_PART_UCI) != 0) {
                         contactElement.setContactId(addr);
                     }
                 } else {
-                    contactElement = BluetoothMapConvoContactElement
-                            .createFromMapContact(contact, addr);
+                    contactElement =
+                            BluetoothMapConvoContactElement.createFromMapContact(contact, addr);
                     // Remove the parameters not to be reported
-                    if((parameterMask & CONVO_PARAM_MASK_PART_UCI) == 0) {
+                    if ((parameterMask & CONVO_PARAM_MASK_PART_UCI) == 0) {
                         contactElement.setContactId(null);
                     }
-                    if((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) == 0) {
+                    if ((parameterMask & CONVO_PARAM_MASK_PART_X_BT_UID) == 0) {
                         contactElement.setBtUid(null);
                     }
-                    if((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) == 0) {
+                    if ((parameterMask & CONVO_PARAM_MASK_PART_DISP_NAME) == 0) {
                         contactElement.setDisplayName(null);
                     }
                 }
@@ -3279,50 +3541,51 @@
      */
     private String getFolderName(int type, int threadId) {
 
-        if(threadId == -1)
+        if (threadId == -1) {
             return BluetoothMapContract.FOLDER_NAME_DELETED;
+        }
 
-        switch(type) {
-        case 1:
-            return BluetoothMapContract.FOLDER_NAME_INBOX;
-        case 2:
-            return BluetoothMapContract.FOLDER_NAME_SENT;
-        case 3:
-            return BluetoothMapContract.FOLDER_NAME_DRAFT;
-        case 4: // Just name outbox, failed and queued "outbox"
-        case 5:
-        case 6:
-            return BluetoothMapContract.FOLDER_NAME_OUTBOX;
+        switch (type) {
+            case 1:
+                return BluetoothMapContract.FOLDER_NAME_INBOX;
+            case 2:
+                return BluetoothMapContract.FOLDER_NAME_SENT;
+            case 3:
+                return BluetoothMapContract.FOLDER_NAME_DRAFT;
+            case 4: // Just name outbox, failed and queued "outbox"
+            case 5:
+            case 6:
+                return BluetoothMapContract.FOLDER_NAME_OUTBOX;
         }
         return "";
     }
 
     public byte[] getMessage(String handle, BluetoothMapAppParams appParams,
             BluetoothMapFolderElement folderElement, String version)
-            throws UnsupportedEncodingException{
+            throws UnsupportedEncodingException {
         TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle);
         mMessageVersion = version;
         long id = BluetoothMapUtils.getCpHandle(handle);
-        if(appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) {
-            throw new IllegalArgumentException("FRACTION_REQUEST_NEXT does not make sence as" +
-                                               " we always return the full message.");
+        if (appParams.getFractionRequest() == BluetoothMapAppParams.FRACTION_REQUEST_NEXT) {
+            throw new IllegalArgumentException("FRACTION_REQUEST_NEXT does not make sence as"
+                    + " we always return the full message.");
         }
-        switch(type) {
-        case SMS_GSM:
-        case SMS_CDMA:
-            return getSmsMessage(id, appParams.getCharset());
-        case MMS:
-            return getMmsMessage(id, appParams);
-        case EMAIL:
-            return getEmailMessage(id, appParams, folderElement);
-        case IM:
-            return getIMMessage(id, appParams, folderElement);
+        switch (type) {
+            case SMS_GSM:
+            case SMS_CDMA:
+                return getSmsMessage(id, appParams.getCharset());
+            case MMS:
+                return getMmsMessage(id, appParams);
+            case EMAIL:
+                return getEmailMessage(id, appParams, folderElement);
+            case IM:
+                return getIMMessage(id, appParams, folderElement);
         }
         throw new IllegalArgumentException("Invalid message handle.");
     }
 
-    private String setVCardFromPhoneNumber(BluetoothMapbMessage message,
-            String phone, boolean incoming) {
+    private String setVCardFromPhoneNumber(BluetoothMapbMessage message, String phone,
+            boolean incoming) {
         String contactId = null, contactName = null;
         String[] phoneNumbers = new String[1];
         //Handle possible exception for empty phone address
@@ -3337,9 +3600,8 @@
         String[] emailAddresses = null;
         Cursor p;
 
-        Uri uri = Uri
-                .withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
-                Uri.encode(phone));
+        Uri uri =
+                Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phone));
 
         String[] projection = {Contacts._ID, Contacts.DISPLAY_NAME};
         String selection = Contacts.IN_VISIBLE_GROUP + "=1";
@@ -3362,14 +3624,13 @@
             try {
                 q = mResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
                         ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
-                        new String[]{contactId},
-                        null);
+                        new String[]{contactId}, null);
                 if (q != null && q.moveToFirst()) {
                     int i = 0;
                     emailAddresses = new String[q.getCount()];
                     do {
-                        String emailAddress = q.getString(q.getColumnIndex(
-                                ContactsContract.CommonDataKinds.Email.ADDRESS));
+                        String emailAddress = q.getString(
+                                q.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS));
                         emailAddresses[i++] = emailAddress;
                     } while (q != null && q.moveToNext());
                 }
@@ -3378,14 +3639,20 @@
             }
         }
 
-        if (incoming == true) {
-            if(V) Log.d(TAG, "Adding originator for phone:" + phone);
+        if (incoming) {
+            if (V) {
+                Log.d(TAG, "Adding originator for phone:" + phone);
+            }
             // Use version 3.0 as we only have a formatted name
-            message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses,null,null);
+            message.addOriginator(contactName, contactName, phoneNumbers, emailAddresses, null,
+                    null);
         } else {
-            if(V) Log.d(TAG, "Adding recipient for phone:" + phone);
+            if (V) {
+                Log.d(TAG, "Adding recipient for phone:" + phone);
+            }
             // Use version 3.0 as we only have a formatted name
-            message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses,null,null);
+            message.addRecipient(contactName, contactName, phoneNumbers, emailAddresses, null,
+                    null);
         }
         return contactName;
     }
@@ -3393,34 +3660,38 @@
     public static final int MAP_MESSAGE_CHARSET_NATIVE = 0;
     public static final int MAP_MESSAGE_CHARSET_UTF8 = 1;
 
-    public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException{
+    public byte[] getSmsMessage(long id, int charset) throws UnsupportedEncodingException {
         int type, threadId;
         long time = -1;
         String msgBody;
         BluetoothMapbMessageSms message = new BluetoothMapbMessageSms();
-        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        TelephonyManager tm =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
 
         Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, "_ID = " + id, null, null);
         if (c == null || !c.moveToFirst()) {
             throw new IllegalArgumentException("SMS handle not found");
         }
 
-        try{
-            if(c != null && c.moveToFirst())
-            {
-                if(V) Log.v(TAG,"c.count: " + c.getCount());
+        try {
+            if (c != null && c.moveToFirst()) {
+                if (V) {
+                    Log.v(TAG, "c.count: " + c.getCount());
+                }
 
-                if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
-                    message.setType(TYPE.SMS_GSM);
-                } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+                if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
                     message.setType(TYPE.SMS_CDMA);
+                } else {
+                    // set SMS_GSM by default
+                    message.setType(TYPE.SMS_GSM);
                 }
                 message.setVersionString(mMessageVersion);
                 String read = c.getString(c.getColumnIndex(Sms.READ));
-                if (read.equalsIgnoreCase("1"))
+                if (read.equalsIgnoreCase("1")) {
                     message.setStatus(true);
-                else
+                } else {
                     message.setStatus(false);
+                }
 
                 type = c.getInt(c.getColumnIndex(Sms.TYPE));
                 threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
@@ -3431,27 +3702,30 @@
                 String phone = c.getString(c.getColumnIndex(Sms.ADDRESS));
                 if ((phone == null) && type == Sms.MESSAGE_TYPE_DRAFT) {
                     //Fetch address for Drafts folder from "canonical_address" table
-                    phone  = getCanonicalAddressSms(mResolver, threadId);
+                    phone = getCanonicalAddressSms(mResolver, threadId);
                 }
                 time = c.getLong(c.getColumnIndex(Sms.DATE));
-                if(type == 1) // Inbox message needs to set the vCard as originator
+                if (type == 1) { // Inbox message needs to set the vCard as originator
                     setVCardFromPhoneNumber(message, phone, true);
-                else          // Other messages sets the vCard as the recipient
+                } else { // Other messages sets the vCard as the recipient
                     setVCardFromPhoneNumber(message, phone, false);
-
-                if(charset == MAP_MESSAGE_CHARSET_NATIVE) {
-                    if(type == 1) //Inbox
-                        message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody,
-                                    phone, time));
-                    else
+                }
+                if (charset == MAP_MESSAGE_CHARSET_NATIVE) {
+                    if (type == 1) { //Inbox
+                        message.setSmsBodyPdus(
+                                BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time));
+                    } else {
                         message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
+                    }
                 } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
                     message.setSmsBody(msgBody);
                 }
                 return message.encode();
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
 
         return message.encode();
@@ -3464,38 +3738,41 @@
         Uri uriAddress = Uri.parse(uriStr);
         String contactName = null;
 
-        Cursor c = mResolver.query( uriAddress, projection, selection, null, null);
+        Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
         try {
             if (c.moveToFirst()) {
                 do {
                     String address = c.getString(c.getColumnIndex(Mms.Addr.ADDRESS));
-                    if(address.equals(INSERT_ADDRES_TOKEN))
+                    if (address.equals(INSERT_ADDRES_TOKEN)) {
                         continue;
-                    Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE));
-                    switch(type) {
-                    case MMS_FROM:
-                        contactName = setVCardFromPhoneNumber(message, address, true);
-                        message.addFrom(contactName, address);
-                        break;
-                    case MMS_TO:
-                        contactName = setVCardFromPhoneNumber(message, address, false);
-                        message.addTo(contactName, address);
-                        break;
-                    case MMS_CC:
-                        contactName = setVCardFromPhoneNumber(message, address, false);
-                        message.addCc(contactName, address);
-                        break;
-                    case MMS_BCC:
-                        contactName = setVCardFromPhoneNumber(message, address, false);
-                        message.addBcc(contactName, address);
-                        break;
-                    default:
-                        break;
                     }
-                } while(c.moveToNext());
+                    Integer type = c.getInt(c.getColumnIndex(Mms.Addr.TYPE));
+                    switch (type) {
+                        case MMS_FROM:
+                            contactName = setVCardFromPhoneNumber(message, address, true);
+                            message.addFrom(contactName, address);
+                            break;
+                        case MMS_TO:
+                            contactName = setVCardFromPhoneNumber(message, address, false);
+                            message.addTo(contactName, address);
+                            break;
+                        case MMS_CC:
+                            contactName = setVCardFromPhoneNumber(message, address, false);
+                            message.addCc(contactName, address);
+                            break;
+                        case MMS_BCC:
+                            contactName = setVCardFromPhoneNumber(message, address, false);
+                            message.addBcc(contactName, address);
+                            break;
+                        default:
+                            break;
+                    }
+                } while (c.moveToNext());
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
     }
 
@@ -3507,7 +3784,7 @@
      * @return
      */
     private byte[] readRawDataPart(Uri contentPartUri, long partid) {
-        String uriStr = new String(contentPartUri+"/"+ partid);
+        String uriStr = new String(contentPartUri + "/" + partid);
         Uri uriAddress = Uri.parse(uriStr);
         InputStream is = null;
         ByteArrayOutputStream os = new ByteArrayOutputStream();
@@ -3519,12 +3796,12 @@
             is = mResolver.openInputStream(uriAddress);
             int len = 0;
             while ((len = is.read(buffer)) != -1) {
-              os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize
+                os.write(buffer, 0, len); // We need to specify the len, as it can be != bufferSize
             }
             retVal = os.toByteArray();
         } catch (IOException e) {
             // do nothing for now
-            Log.w(TAG,"Error reading part data",e);
+            Log.w(TAG, "Error reading part data", e);
         } finally {
             close(os);
             close(is);
@@ -3537,13 +3814,12 @@
      * @param id the content provider ID of the message
      * @param message the bMessage object to add the information to
      */
-    private void extractMmsParts(long id, BluetoothMapbMessageMime message)
-    {
+    private void extractMmsParts(long id, BluetoothMapbMessageMime message) {
         /* Handling of filtering out non-text parts for exclude
          * attachments is handled within the bMessage object. */
         final String[] projection = null;
         String selection = new String(Mms.Part.MSG_ID + "=" + id);
-        String uriStr = new String(Mms.CONTENT_URI + "/"+ id + "/part");
+        String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part");
         Uri uriAddress = Uri.parse(uriStr);
         BluetoothMapbMessageMime.MimePart part;
         Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
@@ -3561,16 +3837,13 @@
                     String cl = c.getString(c.getColumnIndex(Mms.Part.CONTENT_LOCATION));
                     String cdisp = c.getString(c.getColumnIndex(Mms.Part.CONTENT_DISPOSITION));
 
-                    if(V) Log.d(TAG, "     _id : " + partId +
-                            "\n     ct : " + contentType +
-                            "\n     partname : " + name +
-                            "\n     charset : " + charset +
-                            "\n     filename : " + filename +
-                            "\n     text : " + text +
-                            "\n     fd : " + fd +
-                            "\n     cid : " + cid +
-                            "\n     cl : " + cl +
-                            "\n     cdisp : " + cdisp);
+                    if (V) {
+                        Log.d(TAG, "     _id : " + partId + "\n     ct : " + contentType
+                                + "\n     partname : " + name + "\n     charset : " + charset
+                                + "\n     filename : " + filename + "\n     text : " + text
+                                + "\n     fd : " + fd + "\n     cid : " + cid + "\n     cl : " + cl
+                                + "\n     cdisp : " + cdisp);
+                    }
 
                     part = message.addMimePart();
                     part.mContentType = contentType;
@@ -3580,102 +3853,106 @@
                     part.mContentDisposition = cdisp;
 
                     try {
-                        if(text != null) {
+                        if (text != null) {
                             part.mData = text.getBytes("UTF-8");
                             part.mCharsetName = "utf-8";
                         } else {
                             part.mData =
-                                    readRawDataPart(Uri.parse(Mms.CONTENT_URI+"/part"), partId);
-                            if(charset != null) {
+                                    readRawDataPart(Uri.parse(Mms.CONTENT_URI + "/part"), partId);
+                            if (charset != null) {
                                 part.mCharsetName =
                                         CharacterSets.getMimeName(Integer.parseInt(charset));
                             }
                         }
                     } catch (NumberFormatException e) {
-                        Log.d(TAG,"extractMmsParts",e);
+                        Log.d(TAG, "extractMmsParts", e);
                         part.mData = null;
                         part.mCharsetName = null;
                     } catch (UnsupportedEncodingException e) {
-                        Log.d(TAG,"extractMmsParts",e);
+                        Log.d(TAG, "extractMmsParts", e);
                         part.mData = null;
                         part.mCharsetName = null;
-                    } finally {
                     }
                     part.mFileName = filename;
-                } while(c.moveToNext());
+                } while (c.moveToNext());
                 message.updateCharset();
             }
 
         } finally {
-            if(c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
     }
+
     /**
      * Read out the mms parts and update the bMessage object provided i {@linkplain message}
      * @param id the content provider ID of the message
      * @param message the bMessage object to add the information to
      */
-    private void extractIMParts(long id, BluetoothMapbMessageMime message)
-    {
+    private void extractIMParts(long id, BluetoothMapbMessageMime message) {
         /* Handling of filtering out non-text parts for exclude
          * attachments is handled within the bMessage object. */
         final String[] projection = null;
         String selection = new String(BluetoothMapContract.MessageColumns._ID + "=" + id);
-        String uriStr = new String(mBaseUri
-                                         + BluetoothMapContract.TABLE_MESSAGE + "/"+ id + "/part");
+        String uriStr =
+                new String(mBaseUri + BluetoothMapContract.TABLE_MESSAGE + "/" + id + "/part");
         Uri uriAddress = Uri.parse(uriStr);
         BluetoothMapbMessageMime.MimePart part;
         Cursor c = mResolver.query(uriAddress, projection, selection, null, null);
-        try{
+        try {
             if (c.moveToFirst()) {
                 do {
                     Long partId = c.getLong(
-                                  c.getColumnIndex(BluetoothMapContract.MessagePartColumns._ID));
+                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns._ID));
                     String charset = c.getString(
-                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CHARSET));
+                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CHARSET));
                     String filename = c.getString(
-                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.FILENAME));
+                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.FILENAME));
                     String text = c.getString(
-                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.TEXT));
+                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.TEXT));
                     String body = c.getString(
-                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.RAW_DATA));
+                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.RAW_DATA));
                     String cid = c.getString(
-                           c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CONTENT_ID));
+                            c.getColumnIndex(BluetoothMapContract.MessagePartColumns.CONTENT_ID));
 
-                    if(V) Log.d(TAG, "     _id : " + partId +
-                            "\n     charset : " + charset +
-                            "\n     filename : " + filename +
-                            "\n     text : " + text +
-                            "\n     cid : " + cid);
+                    if (V) {
+                        Log.d(TAG, "     _id : " + partId + "\n     charset : " + charset
+                                + "\n     filename : " + filename + "\n     text : " + text
+                                + "\n     cid : " + cid);
+                    }
 
                     part = message.addMimePart();
                     part.mContentId = cid;
                     try {
-                        if(text.equalsIgnoreCase("yes")) {
+                        if (text.equalsIgnoreCase("yes")) {
                             part.mData = body.getBytes("UTF-8");
                             part.mCharsetName = "utf-8";
                         } else {
-                            part.mData = readRawDataPart(Uri.parse(mBaseUri
-                                             + BluetoothMapContract.TABLE_MESSAGE_PART) , partId);
-                            if(charset != null)
-                                part.mCharsetName = CharacterSets.getMimeName(
-                                                                        Integer.parseInt(charset));
+                            part.mData = readRawDataPart(
+                                    Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE_PART),
+                                    partId);
+                            if (charset != null) {
+                                part.mCharsetName =
+                                        CharacterSets.getMimeName(Integer.parseInt(charset));
+                            }
                         }
                     } catch (NumberFormatException e) {
-                        Log.d(TAG,"extractIMParts",e);
+                        Log.d(TAG, "extractIMParts", e);
                         part.mData = null;
                         part.mCharsetName = null;
                     } catch (UnsupportedEncodingException e) {
-                        Log.d(TAG,"extractIMParts",e);
+                        Log.d(TAG, "extractIMParts", e);
                         part.mData = null;
                         part.mCharsetName = null;
-                    } finally {
                     }
                     part.mFileName = filename;
-                } while(c.moveToNext());
+                } while (c.moveToNext());
             }
         } finally {
-            if(c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
 
         message.updateCharset();
@@ -3689,27 +3966,28 @@
      * @throws UnsupportedEncodingException if UTF-8 is not supported,
      * which is guaranteed to be supported on an android device
      */
-    public byte[] getMmsMessage(long id,BluetoothMapAppParams appParams)
-                                                        throws UnsupportedEncodingException {
+    public byte[] getMmsMessage(long id, BluetoothMapAppParams appParams)
+            throws UnsupportedEncodingException {
         int msgBox, threadId;
-        if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
-            throw new IllegalArgumentException("MMS charset native not allowed for MMS"
-                                                                            +" - must be utf-8");
+        if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) {
+            throw new IllegalArgumentException(
+                    "MMS charset native not allowed for MMS" + " - must be utf-8");
+        }
 
         BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
         Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null);
         try {
-            if(c != null && c.moveToFirst())
-            {
+            if (c != null && c.moveToFirst()) {
                 message.setType(TYPE.MMS);
                 message.setVersionString(mMessageVersion);
 
                 // The MMS info:
                 String read = c.getString(c.getColumnIndex(Mms.READ));
-                if (read.equalsIgnoreCase("1"))
+                if (read.equalsIgnoreCase("1")) {
                     message.setStatus(true);
-                else
+                } else {
                     message.setStatus(false);
+                }
 
                 msgBox = c.getInt(c.getColumnIndex(Mms.MESSAGE_BOX));
                 threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
@@ -3718,8 +3996,8 @@
                 message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID)));
                 message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE)));
                 message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L);
-                message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true);
-                message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
+                message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) != 0);
+                message.setIncludeAttachments(appParams.getAttachment() != 0);
                 // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
                 // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
 
@@ -3733,302 +4011,336 @@
                 return message.encode();
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
 
         return message.encode();
     }
 
     /**
-    *
-    * @param id the content provider id for the message to fetch.
-    * @param appParams The application parameter object received from the client.
-    * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
-    * @throws UnsupportedEncodingException if UTF-8 is not supported,
-    * which is guaranteed to be supported on an android device
-    */
-   public byte[] getEmailMessage(long id, BluetoothMapAppParams appParams,
-           BluetoothMapFolderElement currentFolder) throws UnsupportedEncodingException {
-       // Log print out of application parameters set
-       if(D && appParams != null) {
-           Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() +
-                   ", Charset = " + appParams.getCharset() +
-                   ", FractionRequest = " + appParams.getFractionRequest());
-       }
+     *
+     * @param id the content provider id for the message to fetch.
+     * @param appParams The application parameter object received from the client.
+     * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
+     * @throws UnsupportedEncodingException if UTF-8 is not supported,
+     * which is guaranteed to be supported on an android device
+     */
+    public byte[] getEmailMessage(long id, BluetoothMapAppParams appParams,
+            BluetoothMapFolderElement currentFolder) throws UnsupportedEncodingException {
+        // Log print out of application parameters set
+        if (D && appParams != null) {
+            Log.d(TAG,
+                    "TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() + ", Charset = "
+                            + appParams.getCharset() + ", FractionRequest = "
+                            + appParams.getFractionRequest());
+        }
 
-       // Throw exception if requester NATIVE charset for Email
-       // Exception is caught by MapObexServer sendGetMessageResp
-       if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
-           throw new IllegalArgumentException("EMAIL charset not UTF-8");
+        // Throw exception if requester NATIVE charset for Email
+        // Exception is caught by MapObexServer sendGetMessageResp
+        if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) {
+            throw new IllegalArgumentException("EMAIL charset not UTF-8");
+        }
 
-       BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail();
-       Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-       Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = "
-               + id, null, null);
-       try {
-           if(c != null && c.moveToFirst())
-           {
-               BluetoothMapFolderElement folderElement;
-               FileInputStream is = null;
-               ParcelFileDescriptor fd = null;
-               try {
-                   // Handle fraction requests
-                   int fractionRequest = appParams.getFractionRequest();
-                   if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
-                       // Fraction requested
-                       if(V) {
-                           String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT";
-                           Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr
-                                   +  " - send compete message" );
-                       }
-                       // Check if message is complete and if not - request message from server
-                       if (c.getString(c.getColumnIndex(
-                               BluetoothMapContract.MessageColumns.RECEPTION_STATE)).equalsIgnoreCase(
-                                       BluetoothMapContract.RECEPTION_STATE_COMPLETE) == false)  {
-                           // TODO: request message from server
-                           Log.w(TAG, "getEmailMessage - receptionState not COMPLETE -  Not Implemented!" );
-                       }
-                   }
-                   // Set read status:
-                   String read = c.getString(
-                                        c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
-                   if (read != null && read.equalsIgnoreCase("1"))
-                       message.setStatus(true);
-                   else
-                       message.setStatus(false);
+        BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail();
+        Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+        Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
+                "_ID = " + id, null, null);
+        try {
+            if (c != null && c.moveToFirst()) {
+                BluetoothMapFolderElement folderElement;
+                FileInputStream is = null;
+                ParcelFileDescriptor fd = null;
+                try {
+                    // Handle fraction requests
+                    int fractionRequest = appParams.getFractionRequest();
+                    if (fractionRequest != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+                        // Fraction requested
+                        if (V) {
+                            String fractionStr = (fractionRequest == 0) ? "FIRST" : "NEXT";
+                            Log.v(TAG, "getEmailMessage - FractionRequest " + fractionStr
+                                    + " - send compete message");
+                        }
+                        // Check if message is complete and if not - request message from server
+                        if (!c.getString(c.getColumnIndex(
+                                BluetoothMapContract.MessageColumns.RECEPTION_STATE))
+                                .equalsIgnoreCase(BluetoothMapContract.RECEPTION_STATE_COMPLETE)) {
+                            // TODO: request message from server
+                            Log.w(TAG, "getEmailMessage - receptionState not COMPLETE -  Not "
+                                    + "Implemented!");
+                        }
+                    }
+                    // Set read status:
+                    String read = c.getString(
+                            c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
+                    if (read != null && read.equalsIgnoreCase("1")) {
+                        message.setStatus(true);
+                    } else {
+                        message.setStatus(false);
+                    }
 
-                   // Set message type:
-                   message.setType(TYPE.EMAIL);
-                   message.setVersionString(mMessageVersion);
-                   // Set folder:
-                   long folderId = c.getLong(
-                                       c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
-                   folderElement = currentFolder.getFolderById(folderId);
-                   message.setCompleteFolder(folderElement.getFullPath());
+                    // Set message type:
+                    message.setType(TYPE.EMAIL);
+                    message.setVersionString(mMessageVersion);
+                    // Set folder:
+                    long folderId = c.getLong(
+                            c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
+                    folderElement = currentFolder.getFolderById(folderId);
+                    message.setCompleteFolder(folderElement.getFullPath());
 
-                   // Set recipient:
-                   String nameEmail = c.getString(
-                                       c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST));
-                   Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(nameEmail);
-                   if (tokens.length != 0) {
-                       if(D) Log.d(TAG, "Recipient count= " + tokens.length);
-                       int i = 0;
-                       while (i < tokens.length) {
-                           if(V) Log.d(TAG, "Recipient = " + tokens[i].toString());
-                           String[] emails = new String[1];
-                           emails[0] = tokens[i].getAddress();
-                           String name = tokens[i].getName();
-                           message.addRecipient(name, name, null, emails, null, null);
-                           i++;
-                       }
-                   }
+                    // Set recipient:
+                    String nameEmail = c.getString(
+                            c.getColumnIndex(BluetoothMapContract.MessageColumns.TO_LIST));
+                    Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(nameEmail);
+                    if (tokens.length != 0) {
+                        if (D) {
+                            Log.d(TAG, "Recipient count= " + tokens.length);
+                        }
+                        int i = 0;
+                        while (i < tokens.length) {
+                            if (V) {
+                                Log.d(TAG, "Recipient = " + tokens[i].toString());
+                            }
+                            String[] emails = new String[1];
+                            emails[0] = tokens[i].getAddress();
+                            String name = tokens[i].getName();
+                            message.addRecipient(name, name, null, emails, null, null);
+                            i++;
+                        }
+                    }
 
-                   // Set originator:
-                   nameEmail = c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST));
-                   tokens = Rfc822Tokenizer.tokenize(nameEmail);
-                   if (tokens.length != 0) {
-                       if(D) Log.d(TAG, "Originator count= " + tokens.length);
-                       int i = 0;
-                       while (i < tokens.length) {
-                           if(V) Log.d(TAG, "Originator = " + tokens[i].toString());
-                           String[] emails = new String[1];
-                           emails[0] = tokens[i].getAddress();
-                           String name = tokens[i].getName();
-                           message.addOriginator(name, name, null, emails, null, null);
-                           i++;
-                       }
-                   }
-               } finally {
-                   if(c != null) c.close();
-               }
-               // Find out if we get attachments
-               String attStr = (appParams.getAttachment() == 0) ?
-                                           "/" +  BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : "";
-               Uri uri = Uri.parse(contentUri + "/" + id + attStr);
+                    // Set originator:
+                    nameEmail = c.getString(
+                            c.getColumnIndex(BluetoothMapContract.MessageColumns.FROM_LIST));
+                    tokens = Rfc822Tokenizer.tokenize(nameEmail);
+                    if (tokens.length != 0) {
+                        if (D) {
+                            Log.d(TAG, "Originator count= " + tokens.length);
+                        }
+                        int i = 0;
+                        while (i < tokens.length) {
+                            if (V) {
+                                Log.d(TAG, "Originator = " + tokens[i].toString());
+                            }
+                            String[] emails = new String[1];
+                            emails[0] = tokens[i].getAddress();
+                            String name = tokens[i].getName();
+                            message.addOriginator(name, name, null, emails, null, null);
+                            i++;
+                        }
+                    }
+                } finally {
+                    if (c != null) {
+                        c.close();
+                    }
+                }
+                // Find out if we get attachments
+                String attStr = (appParams.getAttachment() == 0) ? "/"
+                        + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS : "";
+                Uri uri = Uri.parse(contentUri + "/" + id + attStr);
 
-               // Get email message body content
-               int count = 0;
-               try {
-                   fd = mResolver.openFileDescriptor(uri, "r");
-                   is = new FileInputStream(fd.getFileDescriptor());
-                   StringBuilder email = new StringBuilder("");
-                   byte[] buffer = new byte[1024];
-                   while((count = is.read(buffer)) != -1) {
-                       // TODO: Handle breaks within a UTF8 character
-                       email.append(new String(buffer,0,count));
-                       if(V) Log.d(TAG, "Email part = "
-                                         + new String(buffer,0,count)
-                                         + " count=" + count);
-                   }
-                   // Set email message body:
-                   message.setEmailBody(email.toString());
-               } catch (FileNotFoundException e) {
-                   Log.w(TAG, e);
-               } catch (NullPointerException e) {
-                   Log.w(TAG, e);
-               } catch (IOException e) {
-                   Log.w(TAG, e);
-               } finally {
-                   try {
-                       if(is != null) is.close();
-                   } catch (IOException e) {}
-                   try {
-                       if(fd != null) fd.close();
-                   } catch (IOException e) {}
-               }
-               return message.encode();
-           }
-       } finally {
-           if (c != null) c.close();
-       }
-       throw new IllegalArgumentException("EMAIL handle not found");
-   }
-   /**
-   *
-   * @param id the content provider id for the message to fetch.
-   * @param appParams The application parameter object received from the client.
-   * @return a byte[] containing the UTF-8 encoded bMessage to send to the client.
-   * @throws UnsupportedEncodingException if UTF-8 is not supported,
-   * which is guaranteed to be supported on an android device
-   */
+                // Get email message body content
+                int count = 0;
+                try {
+                    fd = mResolver.openFileDescriptor(uri, "r");
+                    is = new FileInputStream(fd.getFileDescriptor());
+                    StringBuilder email = new StringBuilder("");
+                    byte[] buffer = new byte[1024];
+                    while ((count = is.read(buffer)) != -1) {
+                        // TODO: Handle breaks within a UTF8 character
+                        email.append(new String(buffer, 0, count));
+                        if (V) {
+                            Log.d(TAG, "Email part = " + new String(buffer, 0, count) + " count="
+                                    + count);
+                        }
+                    }
+                    // Set email message body:
+                    message.setEmailBody(email.toString());
+                } catch (FileNotFoundException e) {
+                    Log.w(TAG, e);
+                } catch (NullPointerException e) {
+                    Log.w(TAG, e);
+                } catch (IOException e) {
+                    Log.w(TAG, e);
+                } finally {
+                    try {
+                        if (is != null) {
+                            is.close();
+                        }
+                    } catch (IOException e) {
+                    }
+                    try {
+                        if (fd != null) {
+                            fd.close();
+                        }
+                    } catch (IOException e) {
+                    }
+                }
+                return message.encode();
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        throw new IllegalArgumentException("EMAIL handle not found");
+    }
+    /**
+     *
+     * @param id the content provider id for the message to fetch.
+     * @param appParams The application parameter object received from the client.
+     * @return a byte[] containing the UTF-8 encoded bMessage to send to the client.
+     * @throws UnsupportedEncodingException if UTF-8 is not supported,
+     * which is guaranteed to be supported on an android device
+     */
 
-   /**
-   *
-   * @param id the content provider id for the message to fetch.
-   * @param appParams The application parameter object received from the client.
-   * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
-   * @throws UnsupportedEncodingException if UTF-8 is not supported,
-   * which is guaranteed to be supported on an android device
-   */
-   public byte[] getIMMessage(long id,
-           BluetoothMapAppParams appParams,
-           BluetoothMapFolderElement folderElement)
-                   throws UnsupportedEncodingException {
-       long threadId, folderId;
+    /**
+     *
+     * @param id the content provider id for the message to fetch.
+     * @param appParams The application parameter object received from the client.
+     * @return a byte[] containing the utf-8 encoded bMessage to send to the client.
+     * @throws UnsupportedEncodingException if UTF-8 is not supported,
+     * which is guaranteed to be supported on an android device
+     */
+    public byte[] getIMMessage(long id, BluetoothMapAppParams appParams,
+            BluetoothMapFolderElement folderElement) throws UnsupportedEncodingException {
+        long threadId, folderId;
 
-       if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE)
-           throw new IllegalArgumentException(
-                   "IM charset native not allowed for IM - must be utf-8");
+        if (appParams.getCharset() == MAP_MESSAGE_CHARSET_NATIVE) {
+            throw new IllegalArgumentException(
+                    "IM charset native not allowed for IM - must be utf-8");
+        }
 
-       BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
-       Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-       Cursor c = mResolver.query(contentUri,
-               BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
-       Cursor contacts = null;
-       try {
-           if(c != null && c.moveToFirst()) {
-               message.setType(TYPE.IM);
-               message.setVersionString(mMessageVersion);
+        BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
+        Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
+        Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
+                "_ID = " + id, null, null);
+        Cursor contacts = null;
+        try {
+            if (c != null && c.moveToFirst()) {
+                message.setType(TYPE.IM);
+                message.setVersionString(mMessageVersion);
 
-               // The IM message info:
-               int read =
-                       c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
-               if (read == 1)
-                   message.setStatus(true);
-               else
-                   message.setStatus(false);
+                // The IM message info:
+                int read =
+                        c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
+                if (read == 1) {
+                    message.setStatus(true);
+                } else {
+                    message.setStatus(false);
+                }
 
-               threadId =
-                       c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID));
-               folderId =
-                       c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
-               folderElement = folderElement.getFolderById(folderId);
-               message.setCompleteFolder(folderElement.getFullPath());
-               message.setSubject(c.getString(
-                       c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT)));
-               message.setMessageId(c.getString(
-                       c.getColumnIndex(BluetoothMapContract.MessageColumns._ID)));
-               message.setDate(c.getLong(
-                       c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE)));
-               message.setTextOnly(c.getInt(c.getColumnIndex(
-                       BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE)) != 0 ? false : true);
+                threadId =
+                        c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.THREAD_ID));
+                folderId =
+                        c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
+                folderElement = folderElement.getFolderById(folderId);
+                message.setCompleteFolder(folderElement.getFullPath());
+                message.setSubject(
+                        c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns.SUBJECT)));
+                message.setMessageId(
+                        c.getString(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID)));
+                message.setDate(
+                        c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns.DATE)));
+                message.setTextOnly(c.getInt(
+                        c.getColumnIndex(BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE))
+                        == 0);
 
-               message.setIncludeAttachments(appParams.getAttachment() == 0 ? false : true);
+                message.setIncludeAttachments(appParams.getAttachment() != 0);
 
-               // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
-               // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
+                // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used
+                // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is
 
-               // The parts
+                // The parts
 
-               //FIXME use the parts when ready - until then use the body column for text-only
-               //  extractIMParts(id, message);
-               //FIXME next few lines are temporary code
-               MimePart part = message.addMimePart();
-               part.mData = c.getString((c.getColumnIndex(
-                       BluetoothMapContract.MessageColumns.BODY))).getBytes("UTF-8");
-               part.mCharsetName = "utf-8";
-               part.mContentId = "0";
-               part.mContentType = "text/plain";
-               message.updateCharset();
-               // FIXME end temp code
+                //FIXME use the parts when ready - until then use the body column for text-only
+                //  extractIMParts(id, message);
+                //FIXME next few lines are temporary code
+                MimePart part = message.addMimePart();
+                part.mData =
+                        c.getString((c.getColumnIndex(BluetoothMapContract.MessageColumns.BODY)))
+                                .getBytes("UTF-8");
+                part.mCharsetName = "utf-8";
+                part.mContentId = "0";
+                part.mContentType = "text/plain";
+                message.updateCharset();
+                // FIXME end temp code
 
-               Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
-               contacts = mResolver.query(contactsUri,
-                       BluetoothMapContract.BT_CONTACT_PROJECTION,
-                       BluetoothMapContract.ConvoContactColumns.CONVO_ID
-                       + " = " + threadId, null, null);
-               // TODO this will not work for group-chats
-               if(contacts != null && contacts.moveToFirst()){
-                   String name = contacts.getString(contacts.getColumnIndex(
-                           BluetoothMapContract.ConvoContactColumns.NAME));
-                   String btUid[] = new String[1];
-                   btUid[0]= contacts.getString(contacts.getColumnIndex(
-                           BluetoothMapContract.ConvoContactColumns.X_BT_UID));
-                   String nickname = contacts.getString(contacts.getColumnIndex(
-                           BluetoothMapContract.ConvoContactColumns.NICKNAME));
-                   String btUci[] = new String[1];
-                   String btOwnUci[] = new String[1];
-                   btOwnUci[0] = mAccount.getUciFull();
-                   btUci[0] = contacts.getString(contacts.getColumnIndex(
-                           BluetoothMapContract.ConvoContactColumns.UCI));
-                   if(folderId == BluetoothMapContract.FOLDER_ID_SENT
-                           || folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) {
-                       message.addRecipient(nickname,name,null, null, btUid, btUci);
-                       message.addOriginator(null, btOwnUci);
+                Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
+                contacts = mResolver.query(contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION,
+                        BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = " + threadId, null,
+                        null);
+                // TODO this will not work for group-chats
+                if (contacts != null && contacts.moveToFirst()) {
+                    String name = contacts.getString(
+                            contacts.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
+                    String[] btUid = new String[1];
+                    btUid[0] = contacts.getString(contacts.getColumnIndex(
+                            BluetoothMapContract.ConvoContactColumns.X_BT_UID));
+                    String nickname = contacts.getString(contacts.getColumnIndex(
+                            BluetoothMapContract.ConvoContactColumns.NICKNAME));
+                    String[] btUci = new String[1];
+                    String[] btOwnUci = new String[1];
+                    btOwnUci[0] = mAccount.getUciFull();
+                    btUci[0] = contacts.getString(
+                            contacts.getColumnIndex(BluetoothMapContract.ConvoContactColumns.UCI));
+                    if (folderId == BluetoothMapContract.FOLDER_ID_SENT
+                            || folderId == BluetoothMapContract.FOLDER_ID_OUTBOX) {
+                        message.addRecipient(nickname, name, null, null, btUid, btUci);
+                        message.addOriginator(null, btOwnUci);
 
-                   }else {
-                       message.addOriginator(nickname,name,null, null, btUid, btUci);
-                       message.addRecipient(null, btOwnUci);
+                    } else {
+                        message.addOriginator(nickname, name, null, null, btUid, btUci);
+                        message.addRecipient(null, btOwnUci);
 
-                   }
-               }
-               return message.encode();
-           }
-       } finally {
-           if(c != null) c.close();
-           if(contacts != null) contacts.close();
-       }
+                    }
+                }
+                return message.encode();
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+            if (contacts != null) {
+                contacts.close();
+            }
+        }
 
-       throw new IllegalArgumentException("IM handle not found");
-   }
+        throw new IllegalArgumentException("IM handle not found");
+    }
 
-   public void setRemoteFeatureMask(int featureMask){
-       this.mRemoteFeatureMask = featureMask;
-       if(V) Log.d(TAG, "setRemoteFeatureMask");
-       if((this.mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
-               == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
-           if(V) Log.d(TAG, "setRemoteFeatureMask MAP_MESSAGE_LISTING_FORMAT_V11");
-           this.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
-       }
-   }
+    public void setRemoteFeatureMask(int featureMask) {
+        this.mRemoteFeatureMask = featureMask;
+        if (V) {
+            Log.d(TAG, "setRemoteFeatureMask");
+        }
+        if ((this.mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
+                == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
+            if (V) {
+                Log.d(TAG, "setRemoteFeatureMask MAP_MESSAGE_LISTING_FORMAT_V11");
+            }
+            this.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+        }
+    }
 
-   public int getRemoteFeatureMask(){
-       return this.mRemoteFeatureMask;
-   }
+    public int getRemoteFeatureMask() {
+        return this.mRemoteFeatureMask;
+    }
 
-    HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
+    HashMap<Long, BluetoothMapConvoListingElement> getSmsMmsConvoList() {
         return mMasInstance.getSmsMmsConvoList();
     }
 
-    void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
+    void setSmsMmsConvoList(HashMap<Long, BluetoothMapConvoListingElement> smsMmsConvoList) {
         mMasInstance.setSmsMmsConvoList(smsMmsConvoList);
     }
 
-    HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
+    HashMap<Long, BluetoothMapConvoListingElement> getImEmailConvoList() {
         return mMasInstance.getImEmailConvoList();
     }
 
-    void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
+    void setImEmailConvoList(HashMap<Long, BluetoothMapConvoListingElement> imEmailConvoList) {
         mMasInstance.setImEmailConvoList(imEmailConvoList);
     }
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index ea88d08..c862619 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -47,21 +47,21 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
-import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
-import android.text.format.DateUtils;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Xml;
 
-import org.xmlpull.v1.XmlSerializer;
-
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
 import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
+
 import com.google.android.mms.pdu.PduHeaders;
 
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -75,6 +75,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 import javax.obex.ResponseCodes;
@@ -86,33 +87,33 @@
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
 
-    private static final String EVENT_TYPE_NEW              = "NewMessage";
-    private static final String EVENT_TYPE_DELETE           = "MessageDeleted";
-    private static final String EVENT_TYPE_REMOVED          = "MessageRemoved";
-    private static final String EVENT_TYPE_SHIFT            = "MessageShift";
+    private static final String EVENT_TYPE_NEW = "NewMessage";
+    private static final String EVENT_TYPE_DELETE = "MessageDeleted";
+    private static final String EVENT_TYPE_REMOVED = "MessageRemoved";
+    private static final String EVENT_TYPE_SHIFT = "MessageShift";
     private static final String EVENT_TYPE_DELEVERY_SUCCESS = "DeliverySuccess";
-    private static final String EVENT_TYPE_SENDING_SUCCESS  = "SendingSuccess";
-    private static final String EVENT_TYPE_SENDING_FAILURE  = "SendingFailure";
+    private static final String EVENT_TYPE_SENDING_SUCCESS = "SendingSuccess";
+    private static final String EVENT_TYPE_SENDING_FAILURE = "SendingFailure";
     private static final String EVENT_TYPE_DELIVERY_FAILURE = "DeliveryFailure";
-    private static final String EVENT_TYPE_READ_STATUS      = "ReadStatusChanged";
-    private static final String EVENT_TYPE_CONVERSATION     = "ConversationChanged";
-    private static final String EVENT_TYPE_PRESENCE         = "ParticipantPresenceChanged";
-    private static final String EVENT_TYPE_CHAT_STATE       = "ParticipantChatStateChanged";
+    private static final String EVENT_TYPE_READ_STATUS = "ReadStatusChanged";
+    private static final String EVENT_TYPE_CONVERSATION = "ConversationChanged";
+    private static final String EVENT_TYPE_PRESENCE = "ParticipantPresenceChanged";
+    private static final String EVENT_TYPE_CHAT_STATE = "ParticipantChatStateChanged";
 
-    private static final long EVENT_FILTER_NEW_MESSAGE                  = 1L;
-    private static final long EVENT_FILTER_MESSAGE_DELETED              = 1L<<1;
-    private static final long EVENT_FILTER_MESSAGE_SHIFT                = 1L<<2;
-    private static final long EVENT_FILTER_SENDING_SUCCESS              = 1L<<3;
-    private static final long EVENT_FILTER_SENDING_FAILED               = 1L<<4;
-    private static final long EVENT_FILTER_DELIVERY_SUCCESS             = 1L<<5;
-    private static final long EVENT_FILTER_DELIVERY_FAILED              = 1L<<6;
-    private static final long EVENT_FILTER_MEMORY_FULL                  = 1L<<7; // Unused
-    private static final long EVENT_FILTER_MEMORY_AVAILABLE             = 1L<<8; // Unused
-    private static final long EVENT_FILTER_READ_STATUS_CHANGED          = 1L<<9;
-    private static final long EVENT_FILTER_CONVERSATION_CHANGED         = 1L<<10;
-    private static final long EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED = 1L<<11;
-    private static final long EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED= 1L<<12;
-    private static final long EVENT_FILTER_MESSAGE_REMOVED              = 1L<<13;
+    private static final long EVENT_FILTER_NEW_MESSAGE = 1L;
+    private static final long EVENT_FILTER_MESSAGE_DELETED = 1L << 1;
+    private static final long EVENT_FILTER_MESSAGE_SHIFT = 1L << 2;
+    private static final long EVENT_FILTER_SENDING_SUCCESS = 1L << 3;
+    private static final long EVENT_FILTER_SENDING_FAILED = 1L << 4;
+    private static final long EVENT_FILTER_DELIVERY_SUCCESS = 1L << 5;
+    private static final long EVENT_FILTER_DELIVERY_FAILED = 1L << 6;
+    private static final long EVENT_FILTER_MEMORY_FULL = 1L << 7; // Unused
+    private static final long EVENT_FILTER_MEMORY_AVAILABLE = 1L << 8; // Unused
+    private static final long EVENT_FILTER_READ_STATUS_CHANGED = 1L << 9;
+    private static final long EVENT_FILTER_CONVERSATION_CHANGED = 1L << 10;
+    private static final long EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED = 1L << 11;
+    private static final long EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED = 1L << 12;
+    private static final long EVENT_FILTER_MESSAGE_REMOVED = 1L << 13;
 
     // TODO: If we are requesting a large message from the network, on a slow connection
     //       20 seconds might not be enough... But then again 20 seconds is long for other
@@ -135,8 +136,8 @@
     // Default event report version is 1.0
     private int mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
 
-    private BluetoothMapFolderElement mFolders =
-            new BluetoothMapFolderElement("DUMMY", null); // Will be set by the MAS when generated.
+    private BluetoothMapFolderElement mFolders = new BluetoothMapFolderElement("DUMMY", null);
+    // Will be set by the MAS when generated.
     private Uri mMessageUri = null;
     private Uri mContactUri = null;
 
@@ -163,7 +164,7 @@
     private static final String ACTION_MESSAGE_DELIVERY =
             "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY";
     /*package*/ static final String ACTION_MESSAGE_SENT =
-        "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT";
+            "com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT";
 
     public static final String EXTRA_MESSAGE_SENT_HANDLE = "HANDLE";
     public static final String EXTRA_MESSAGE_SENT_RESULT = "result";
@@ -180,114 +181,86 @@
     private boolean mInitialized = false;
 
 
-    static final String[] SMS_PROJECTION = new String[] {
-        Sms._ID,
-        Sms.THREAD_ID,
-        Sms.ADDRESS,
-        Sms.BODY,
-        Sms.DATE,
-        Sms.READ,
-        Sms.TYPE,
-        Sms.STATUS,
-        Sms.LOCKED,
-        Sms.ERROR_CODE
+    static final String[] SMS_PROJECTION = new String[]{
+            Sms._ID,
+            Sms.THREAD_ID,
+            Sms.ADDRESS,
+            Sms.BODY,
+            Sms.DATE,
+            Sms.READ,
+            Sms.TYPE,
+            Sms.STATUS,
+            Sms.LOCKED,
+            Sms.ERROR_CODE
     };
 
-    static final String[] SMS_PROJECTION_SHORT = new String[] {
-        Sms._ID,
-        Sms.THREAD_ID,
-        Sms.TYPE,
-        Sms.READ
+    static final String[] SMS_PROJECTION_SHORT = new String[]{
+            Sms._ID, Sms.THREAD_ID, Sms.TYPE, Sms.READ
     };
 
-    static final String[] SMS_PROJECTION_SHORT_EXT = new String[] {
-        Sms._ID,
-        Sms.THREAD_ID,
-        Sms.ADDRESS,
-        Sms.BODY,
-        Sms.DATE,
-        Sms.READ,
-        Sms.TYPE,
+    static final String[] SMS_PROJECTION_SHORT_EXT = new String[]{
+            Sms._ID, Sms.THREAD_ID, Sms.ADDRESS, Sms.BODY, Sms.DATE, Sms.READ, Sms.TYPE,
     };
 
-    static final String[] MMS_PROJECTION_SHORT = new String[] {
-        Mms._ID,
-        Mms.THREAD_ID,
-        Mms.MESSAGE_TYPE,
-        Mms.MESSAGE_BOX,
-        Mms.READ
+    static final String[] MMS_PROJECTION_SHORT = new String[]{
+            Mms._ID, Mms.THREAD_ID, Mms.MESSAGE_TYPE, Mms.MESSAGE_BOX, Mms.READ
     };
 
-    static final String[] MMS_PROJECTION_SHORT_EXT = new String[] {
-        Mms._ID,
-        Mms.THREAD_ID,
-        Mms.MESSAGE_TYPE,
-        Mms.MESSAGE_BOX,
-        Mms.READ,
-        Mms.DATE,
-        Mms.SUBJECT,
-        Mms.PRIORITY
+    static final String[] MMS_PROJECTION_SHORT_EXT = new String[]{
+            Mms._ID,
+            Mms.THREAD_ID,
+            Mms.MESSAGE_TYPE,
+            Mms.MESSAGE_BOX,
+            Mms.READ,
+            Mms.DATE,
+            Mms.SUBJECT,
+            Mms.PRIORITY
     };
 
-    static final String[] MSG_PROJECTION_SHORT = new String[] {
-        BluetoothMapContract.MessageColumns._ID,
-        BluetoothMapContract.MessageColumns.FOLDER_ID,
-        BluetoothMapContract.MessageColumns.FLAG_READ
+    static final String[] MSG_PROJECTION_SHORT = new String[]{
+            BluetoothMapContract.MessageColumns._ID,
+            BluetoothMapContract.MessageColumns.FOLDER_ID,
+            BluetoothMapContract.MessageColumns.FLAG_READ
     };
 
-    static final String[] MSG_PROJECTION_SHORT_EXT = new String[] {
-        BluetoothMapContract.MessageColumns._ID,
-        BluetoothMapContract.MessageColumns.FOLDER_ID,
-        BluetoothMapContract.MessageColumns.FLAG_READ,
-        BluetoothMapContract.MessageColumns.DATE,
-        BluetoothMapContract.MessageColumns.SUBJECT,
-        BluetoothMapContract.MessageColumns.FROM_LIST,
-        BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY
+    static final String[] MSG_PROJECTION_SHORT_EXT = new String[]{
+            BluetoothMapContract.MessageColumns._ID,
+            BluetoothMapContract.MessageColumns.FOLDER_ID,
+            BluetoothMapContract.MessageColumns.FLAG_READ,
+            BluetoothMapContract.MessageColumns.DATE,
+            BluetoothMapContract.MessageColumns.SUBJECT,
+            BluetoothMapContract.MessageColumns.FROM_LIST,
+            BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY
     };
 
-    static final String[] MSG_PROJECTION_SHORT_EXT2 = new String[] {
-        BluetoothMapContract.MessageColumns._ID,
-        BluetoothMapContract.MessageColumns.FOLDER_ID,
-        BluetoothMapContract.MessageColumns.FLAG_READ,
-        BluetoothMapContract.MessageColumns.DATE,
-        BluetoothMapContract.MessageColumns.SUBJECT,
-        BluetoothMapContract.MessageColumns.FROM_LIST,
-        BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
-        BluetoothMapContract.MessageColumns.THREAD_ID,
-        BluetoothMapContract.MessageColumns.THREAD_NAME
+    static final String[] MSG_PROJECTION_SHORT_EXT2 = new String[]{
+            BluetoothMapContract.MessageColumns._ID,
+            BluetoothMapContract.MessageColumns.FOLDER_ID,
+            BluetoothMapContract.MessageColumns.FLAG_READ,
+            BluetoothMapContract.MessageColumns.DATE,
+            BluetoothMapContract.MessageColumns.SUBJECT,
+            BluetoothMapContract.MessageColumns.FROM_LIST,
+            BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+            BluetoothMapContract.MessageColumns.THREAD_ID,
+            BluetoothMapContract.MessageColumns.THREAD_NAME
     };
 
-    public BluetoothMapContentObserver(final Context context,
-            BluetoothMnsObexClient mnsClient,
-            BluetoothMapMasInstance masInstance,
-            BluetoothMapAccountItem account,
+    public BluetoothMapContentObserver(final Context context, BluetoothMnsObexClient mnsClient,
+            BluetoothMapMasInstance masInstance, BluetoothMapAccountItem account,
             boolean enableSmsMms) throws RemoteException {
         mContext = context;
         mResolver = mContext.getContentResolver();
         mAccount = account;
         mMasInstance = masInstance;
         mMasId = mMasInstance.getMasId();
+        setObserverRemoteFeatureMask(mMasInstance.getRemoteFeatureMask());
 
-        mMapSupportedFeatures = mMasInstance.getRemoteFeatureMask();
-        if (D) Log.d(TAG, "BluetoothMapContentObserver: Supported features " +
-                Integer.toHexString(mMapSupportedFeatures) ) ;
-
-        if((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT
-                & mMapSupportedFeatures) != 0){
-            mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
-        }
-        // Make sure support for all formats result in latest version returned
-        if((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT
-                & mMapSupportedFeatures) != 0){
-            mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
-        }
-
-        if(account != null) {
+        if (account != null) {
             mAuthority = Uri.parse(account.mBase_uri).getAuthority();
             mMessageUri = Uri.parse(account.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
             if (mAccount.getType() == TYPE.IM) {
-                mContactUri = Uri.parse(account.mBase_uri + "/"
-                        + BluetoothMapContract.TABLE_CONVOCONTACT);
+                mContactUri = Uri.parse(
+                        account.mBase_uri + "/" + BluetoothMapContract.TABLE_CONVOCONTACT);
             }
             // TODO: We need to release this again!
             mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
@@ -296,7 +269,7 @@
             }
             mProviderClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
             mContactList = mMasInstance.getContactList();
-            if(mContactList == null) {
+            if (mContactList == null) {
                 setContactList(new HashMap<String, BluetoothMapConvoContactElement>(), false);
                 initContactsList();
             }
@@ -307,48 +280,60 @@
         /* Get the cached list - if any, else create */
         mMsgListSms = mMasInstance.getMsgListSms();
         boolean doInit = false;
-        if(mEnableSmsMms) {
-            if(mMsgListSms == null) {
+        if (mEnableSmsMms) {
+            if (mMsgListSms == null) {
                 setMsgListSms(new HashMap<Long, Msg>(), false);
                 doInit = true;
             }
             mMsgListMms = mMasInstance.getMsgListMms();
-            if(mMsgListMms == null) {
+            if (mMsgListMms == null) {
                 setMsgListMms(new HashMap<Long, Msg>(), false);
                 doInit = true;
             }
         }
-        if(mAccount != null) {
+        if (mAccount != null) {
             mMsgListMsg = mMasInstance.getMsgListMsg();
-            if(mMsgListMsg == null) {
+            if (mMsgListMsg == null) {
                 setMsgListMsg(new HashMap<Long, Msg>(), false);
                 doInit = true;
             }
         }
-        if(doInit) {
+        if (doInit) {
             initMsgList();
         }
     }
 
     public int getObserverRemoteFeatureMask() {
-        if (V) Log.v(TAG, "getObserverRemoteFeatureMask : " + mMapEventReportVersion
-            + " mMapSupportedFeatures: " + mMapSupportedFeatures);
+        if (V) {
+            Log.v(TAG, "getObserverRemoteFeatureMask : " + mMapEventReportVersion
+                    + " mMapSupportedFeatures: " + mMapSupportedFeatures);
+        }
         return mMapSupportedFeatures;
     }
 
     public void setObserverRemoteFeatureMask(int remoteSupportedFeatures) {
-        mMapSupportedFeatures = remoteSupportedFeatures;
-        if ((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT
-                & mMapSupportedFeatures) != 0) {
+        mMapSupportedFeatures =
+                remoteSupportedFeatures & BluetoothMapMasInstance.SDP_MAP_MAS_FEATURES;
+        if ((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT & mMapSupportedFeatures)
+                != 0) {
             mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
         }
         // Make sure support for all formats result in latest version returned
-        if ((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT
-                & mMapSupportedFeatures) != 0) {
+        if ((BluetoothMapUtils.MAP_FEATURE_EVENT_REPORT_V12_BIT & mMapSupportedFeatures) != 0) {
             mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+        } else if (((BluetoothMapUtils.MAP_FEATURE_PARTICIPANT_PRESENCE_CHANGE_BIT
+                | BluetoothMapUtils.MAP_FEATURE_PARTICIPANT_CHAT_STATE_CHANGE_BIT)
+                & mMapSupportedFeatures) != 0) {
+            // Warning according to page 46/123 of MAP 1.3 spec
+            Log.w(TAG, "setObserverRemoteFeatureMask: Extended Event Reports 1.2 is not set even"
+                    + "though PARTICIPANT_PRESENCE_CHANGE_BIT or PARTICIPANT_CHAT_STATE_CHANGE_BIT"
+                    + " were set, mMapSupportedFeatures=" + mMapSupportedFeatures);
         }
-        if (V) Log.d(TAG, "setObserverRemoteFeatureMask : " + mMapEventReportVersion
-            + " mMapSupportedFeatures : " + mMapSupportedFeatures);
+        if (D) {
+            Log.d(TAG,
+                    "setObserverRemoteFeatureMask: mMapEventReportVersion=" + mMapEventReportVersion
+                            + " mMapSupportedFeatures=" + mMapSupportedFeatures);
+        }
     }
 
     private Map<Long, Msg> getMsgListSms() {
@@ -357,7 +342,7 @@
 
     private void setMsgListSms(Map<Long, Msg> msgListSms, boolean changesDetected) {
         mMsgListSms = msgListSms;
-        if(changesDetected) {
+        if (changesDetected) {
             mMasInstance.updateFolderVersionCounter();
         }
         mMasInstance.setMsgListSms(msgListSms);
@@ -371,7 +356,7 @@
 
     private void setMsgListMms(Map<Long, Msg> msgListMms, boolean changesDetected) {
         mMsgListMms = msgListMms;
-        if(changesDetected) {
+        if (changesDetected) {
             mMasInstance.updateFolderVersionCounter();
         }
         mMasInstance.setMsgListMms(msgListMms);
@@ -385,7 +370,7 @@
 
     private void setMsgListMsg(Map<Long, Msg> msgListMsg, boolean changesDetected) {
         mMsgListMsg = msgListMsg;
-        if(changesDetected) {
+        if (changesDetected) {
             mMasInstance.updateFolderVersionCounter();
         }
         mMasInstance.setMsgListMsg(msgListMsg);
@@ -404,7 +389,7 @@
     private void setContactList(Map<String, BluetoothMapConvoContactElement> contactList,
             boolean changesDetected) {
         mContactList = contactList;
-        if(changesDetected) {
+        if (changesDetected) {
             mMasInstance.updateImEmailConvoListVersionCounter();
         }
         mMasInstance.setContactList(contactList);
@@ -460,8 +445,8 @@
 
     private TYPE getSmsType() {
         TYPE smsType = null;
-        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
+        TelephonyManager tm =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
 
         if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
             smsType = TYPE.SMS_CDMA;
@@ -480,7 +465,7 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            if(uri == null) {
+            if (uri == null) {
                 Log.w(TAG, "onChange() with URI == null - not handled.");
                 return;
             }
@@ -490,30 +475,34 @@
                 return;
             }
 
-            if (V) Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId()
-                    + " Uri: " + uri.toString() + " selfchange: " + selfChange);
+            if (V) {
+                Log.d(TAG, "onChange on thread: " + Thread.currentThread().getId() + " Uri: "
+                        + uri.toString() + " selfchange: " + selfChange);
+            }
 
-            if(uri.toString().contains(BluetoothMapContract.TABLE_CONVOCONTACT))
+            if (uri.toString().contains(BluetoothMapContract.TABLE_CONVOCONTACT)) {
                 handleContactListChanges(uri);
-            else
+            } else {
                 handleMsgListChanges(uri);
+            }
         }
     };
 
     private static final HashMap<Integer, String> FOLDER_SMS_MAP;
+
     static {
         FOLDER_SMS_MAP = new HashMap<Integer, String>();
-        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_INBOX,  BluetoothMapContract.FOLDER_NAME_INBOX);
-        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_SENT,  BluetoothMapContract.FOLDER_NAME_SENT);
-        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_DRAFT,  BluetoothMapContract.FOLDER_NAME_DRAFT);
-        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_OUTBOX,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
-        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_FAILED,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
-        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_QUEUED,  BluetoothMapContract.FOLDER_NAME_OUTBOX);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_SENT, BluetoothMapContract.FOLDER_NAME_SENT);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_DRAFT, BluetoothMapContract.FOLDER_NAME_DRAFT);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_FAILED, BluetoothMapContract.FOLDER_NAME_OUTBOX);
+        FOLDER_SMS_MAP.put(Sms.MESSAGE_TYPE_QUEUED, BluetoothMapContract.FOLDER_NAME_OUTBOX);
     }
 
     private static String getSmsFolderName(int type) {
         String name = FOLDER_SMS_MAP.get(type);
-        if(name != null) {
+        if (name != null) {
             return name;
         }
         Log.e(TAG, "New SMS mailbox types have been introduced, without an update in BT...");
@@ -522,17 +511,18 @@
 
 
     private static final HashMap<Integer, String> FOLDER_MMS_MAP;
+
     static {
         FOLDER_MMS_MAP = new HashMap<Integer, String>();
-        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_INBOX,  BluetoothMapContract.FOLDER_NAME_INBOX);
-        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_SENT,   BluetoothMapContract.FOLDER_NAME_SENT);
+        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_INBOX, BluetoothMapContract.FOLDER_NAME_INBOX);
+        FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_SENT, BluetoothMapContract.FOLDER_NAME_SENT);
         FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_DRAFTS, BluetoothMapContract.FOLDER_NAME_DRAFT);
         FOLDER_MMS_MAP.put(Mms.MESSAGE_BOX_OUTBOX, BluetoothMapContract.FOLDER_NAME_OUTBOX);
     }
 
     private static String getMmsFolderName(int mailbox) {
         String name = FOLDER_MMS_MAP.get(mailbox);
-        if(name != null) {
+        if (name != null) {
             return name;
         }
         Log.e(TAG, "New MMS mailboxes have been introduced, without an update in BT...");
@@ -548,23 +538,23 @@
     }
 
     private class ConvoContactInfo {
-        public int mConvoColConvoId         = -1;
-        public int mConvoColLastActivity    = -1;
-        public int mConvoColName            = -1;
+        public int mConvoColConvoId = -1;
+        public int mConvoColLastActivity = -1;
+        public int mConvoColName = -1;
         //        public int mConvoColRead            = -1;
         //        public int mConvoColVersionCounter  = -1;
-        public int mContactColUci           = -1;
-        public int mContactColConvoId       = -1;
-        public int mContactColName          = -1;
-        public int mContactColNickname      = -1;
-        public int mContactColBtUid         = -1;
-        public int mContactColChatState     = -1;
-        public int mContactColContactId     = -1;
-        public int mContactColLastActive    = -1;
+        public int mContactColUci = -1;
+        public int mContactColConvoId = -1;
+        public int mContactColName = -1;
+        public int mContactColNickname = -1;
+        public int mContactColBtUid = -1;
+        public int mContactColChatState = -1;
+        public int mContactColContactId = -1;
+        public int mContactColLastActive = -1;
         public int mContactColPresenceState = -1;
-        public int mContactColPresenceText  = -1;
-        public int mContactColPriority      = -1;
-        public int mContactColLastOnline    = -1;
+        public int mContactColPresenceText = -1;
+        public int mContactColPriority = -1;
+        public int mContactColLastOnline = -1;
 
         public void setConvoColunms(Cursor c) {
             //            mConvoColConvoId         = c.getColumnIndex(
@@ -573,59 +563,55 @@
             //                    BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY);
             //            mConvoColName            = c.getColumnIndex(
             //                    BluetoothMapContract.ConversationColumns.THREAD_NAME);
-            mContactColConvoId       = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.CONVO_ID);
-            mContactColName          = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.NAME);
-            mContactColNickname      = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.NICKNAME);
-            mContactColBtUid         = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.X_BT_UID);
-            mContactColChatState     = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
-            mContactColUci           = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.UCI);
-            mContactColNickname      = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.NICKNAME);
-            mContactColLastActive    = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
-            mContactColName          = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.NAME);
-            mContactColPresenceState = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
-            mContactColPresenceText  = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
-            mContactColPriority      = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.PRIORITY);
-            mContactColLastOnline    = c.getColumnIndex(
-                    BluetoothMapContract.ConvoContactColumns.LAST_ONLINE);
+            mContactColConvoId =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.CONVO_ID);
+            mContactColName = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME);
+            mContactColNickname =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NICKNAME);
+            mContactColBtUid = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.X_BT_UID);
+            mContactColChatState =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.CHAT_STATE);
+            mContactColUci = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.UCI);
+            mContactColNickname =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NICKNAME);
+            mContactColLastActive =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE);
+            mContactColName = c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME);
+            mContactColPresenceState =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE);
+            mContactColPresenceText =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.STATUS_TEXT);
+            mContactColPriority =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.PRIORITY);
+            mContactColLastOnline =
+                    c.getColumnIndex(BluetoothMapContract.ConvoContactColumns.LAST_ONLINE);
         }
     }
 
     private class Event {
-        String eventType;
-        long handle;
-        String folder = null;
-        String oldFolder = null;
-        TYPE msgType;
+        public String eventType;
+        public long handle;
+        public String folder = null;
+        public String oldFolder = null;
+        public TYPE msgType;
         /* Extended event parameters in MAP Event version 1.1 */
-        String datetime = null; // OBEX time "YYYYMMDDTHHMMSS"
-        String uci = null;
-        String subject = null;
-        String senderName = null;
-        String priority = null;
+        public String datetime = null; // OBEX time "YYYYMMDDTHHMMSS"
+        public String uci = null;
+        public String subject = null;
+        public String senderName = null;
+        public String priority = null;
         /* Event parameters in MAP Event version 1.2 */
-        String conversationName = null;
-        long conversationID = -1;
-        int presenceState = BluetoothMapContract.PresenceState.UNKNOWN;
-        String presenceStatus = null;
-        int chatState = BluetoothMapContract.ChatState.UNKNOWN;
+        public String conversationName = null;
+        public long conversationID = -1;
+        public int presenceState = BluetoothMapContract.PresenceState.UNKNOWN;
+        public String presenceStatus = null;
+        public int chatState = BluetoothMapContract.ChatState.UNKNOWN;
 
-        final static String PATH = "telecom/msg/";
+        static final String PATH = "telecom/msg/";
 
         private void setFolderPath(String name, TYPE type) {
             if (name != null) {
-                if(type == TYPE.EMAIL || type == TYPE.IM) {
+                if (type == TYPE.EMAIL || type == TYPE.IM) {
                     this.folder = name;
                 } else {
                     this.folder = PATH + name;
@@ -635,13 +621,12 @@
             }
         }
 
-        public Event(String eventType, long handle, String folder,
-                String oldFolder, TYPE msgType) {
+        Event(String eventType, long handle, String folder, String oldFolder, TYPE msgType) {
             this.eventType = eventType;
             this.handle = handle;
             setFolderPath(folder, msgType);
             if (oldFolder != null) {
-                if(msgType == TYPE.EMAIL || msgType == TYPE.IM) {
+                if (msgType == TYPE.EMAIL || msgType == TYPE.IM) {
                     this.oldFolder = oldFolder;
                 } else {
                     this.oldFolder = PATH + oldFolder;
@@ -652,7 +637,7 @@
             this.msgType = msgType;
         }
 
-        public Event(String eventType, long handle, String folder, TYPE msgType) {
+        Event(String eventType, long handle, String folder, TYPE msgType) {
             this.eventType = eventType;
             this.handle = handle;
             setFolderPath(folder, msgType);
@@ -660,8 +645,8 @@
         }
 
         /* extended event type 1.1 */
-        public Event(String eventType, long handle, String folder, TYPE msgType,
-                String datetime, String subject, String senderName, String priority) {
+        Event(String eventType, long handle, String folder, TYPE msgType, String datetime,
+                String subject, String senderName, String priority) {
             this.eventType = eventType;
             this.handle = handle;
             setFolderPath(folder, msgType);
@@ -677,9 +662,9 @@
         }
 
         /* extended event type 1.2 message events */
-        public Event(String eventType, long handle, String folder, TYPE msgType,
-                String datetime, String subject, String senderName, String priority,
-                long conversationID, String conversationName) {
+        Event(String eventType, long handle, String folder, TYPE msgType, String datetime,
+                String subject, String senderName, String priority, long conversationID,
+                String conversationName) {
             this.eventType = eventType;
             this.handle = handle;
             setFolderPath(folder, msgType);
@@ -701,7 +686,7 @@
         }
 
         /* extended event type 1.2 for conversation, presence or chat state changed events */
-        public Event(String eventType, String uci, TYPE msgType, String name, String priority,
+        Event(String eventType, String uci, TYPE msgType, String name, String priority,
                 String lastActivity, long conversationID, String conversationName,
                 int presenceState, String presenceStatus, int chatState) {
             this.eventType = eventType;
@@ -738,18 +723,17 @@
                 xmlEvtReport.startDocument("UTF-8", true);
                 xmlEvtReport.text("\r\n");
                 xmlEvtReport.startTag("", "MAP-event-report");
-                if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
-                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V10_STR);
+                if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V12) {
+                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V12_STR);
                 } else if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
                     xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V11_STR);
                 } else {
-                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V12_STR);
+                    xmlEvtReport.attribute("", "version", BluetoothMapUtils.MAP_V10_STR);
                 }
                 xmlEvtReport.startTag("", "event");
                 xmlEvtReport.attribute("", "type", eventType);
-                if (eventType.equals(EVENT_TYPE_CONVERSATION) ||
-                        eventType.equals(EVENT_TYPE_PRESENCE) ||
-                        eventType.equals(EVENT_TYPE_CHAT_STATE)) {
+                if (eventType.equals(EVENT_TYPE_CONVERSATION) || eventType.equals(
+                        EVENT_TYPE_PRESENCE) || eventType.equals(EVENT_TYPE_CHAT_STATE)) {
                     xmlEvtReport.attribute("", "participant_uci", uci);
                 } else {
                     xmlEvtReport.attribute("", "handle",
@@ -775,10 +759,12 @@
                 }
                 if (subject != null) {
                     xmlEvtReport.attribute("", "subject",
-                            subject.substring(0,subject.length() < 256 ? subject.length() : 256));
+                            subject.substring(0, subject.length() < 256 ? subject.length() : 256));
                 }
                 if (senderName != null) {
-                    xmlEvtReport.attribute("", "sender_name", senderName);
+                    xmlEvtReport.attribute("", "sender_name",
+                            senderName.substring(
+                                    0, senderName.length() < 256 ? senderName.length() : 255));
                 }
                 if (priority != null) {
                     xmlEvtReport.attribute("", "priority", priority);
@@ -786,7 +772,7 @@
 
                 //}
                 /* Include conversation information from event version 1.2 */
-                if (mMapEventReportVersion > BluetoothMapUtils.MAP_EVENT_REPORT_V11 ) {
+                if (mMapEventReportVersion > BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
                     if (conversationName != null) {
                         xmlEvtReport.attribute("", "conversation_name", conversationName);
                     }
@@ -804,8 +790,9 @@
                         if (presenceStatus != null) {
                             // Convert provider conversation handle to string incl type
                             xmlEvtReport.attribute("", "presence_status",
-                                    presenceStatus.substring(
-                                            0,presenceStatus.length() < 256 ? subject.length() : 256));
+                                    presenceStatus.substring(0,
+                                            presenceStatus.length() < 256 ? subject.length()
+                                                    : 256));
                         }
                     }
                     if (eventType.equals(EVENT_TYPE_PRESENCE)) {
@@ -820,36 +807,46 @@
                 xmlEvtReport.endTag("", "MAP-event-report");
                 xmlEvtReport.endDocument();
             } catch (IllegalArgumentException e) {
-                if(D) Log.w(TAG,e);
+                if (D) {
+                    Log.w(TAG, e);
+                }
             } catch (IllegalStateException e) {
-                if(D) Log.w(TAG,e);
+                if (D) {
+                    Log.w(TAG, e);
+                }
             } catch (IOException e) {
-                if(D) Log.w(TAG,e);
+                if (D) {
+                    Log.w(TAG, e);
+                }
             }
 
-            if (V) Log.d(TAG, sw.toString());
+            if (V) {
+                Log.d(TAG, sw.toString());
+            }
 
             return sw.toString().getBytes("UTF-8");
         }
     }
 
     /*package*/ class Msg {
-        long id;
-        int type;               // Used as folder for SMS/MMS
-        int threadId;           // Used for SMS/MMS at delete
-        long folderId = -1;     // Email folder ID
-        long oldFolderId = -1;  // Used for email undelete
-        boolean localInitiatedSend = false; // Used for MMS to filter out events
-        boolean transparent = false; // Used for EMAIL to delete message sent with transparency
-        int flagRead = -1;      // Message status read/unread
+        public long id;
+        public int type;               // Used as folder for SMS/MMS
+        public int threadId;           // Used for SMS/MMS at delete
+        public long folderId = -1;     // Email folder ID
+        public long oldFolderId = -1;  // Used for email undelete
+        public boolean localInitiatedSend = false; // Used for MMS to filter out events
+        public boolean transparent = false;
+        // Used for EMAIL to delete message sent with transparency
+        public int flagRead = -1;      // Message status read/unread
 
-        public Msg(long id, int type, int threadId, int readFlag) {
+        Msg(long id, int type, int threadId, int readFlag) {
             this.id = id;
             this.type = type;
             this.threadId = threadId;
             this.flagRead = readFlag;
         }
-        public Msg(long id, long folderId, int readFlag) {
+
+        Msg(long id, long folderId, int readFlag) {
             this.id = id;
             this.folderId = folderId;
             this.flagRead = readFlag;
@@ -869,15 +866,19 @@
 
         @Override
         public boolean equals(Object obj) {
-            if (this == obj)
+            if (this == obj) {
                 return true;
-            if (obj == null)
+            }
+            if (obj == null) {
                 return false;
-            if (getClass() != obj.getClass())
+            }
+            if (getClass() != obj.getClass()) {
                 return false;
+            }
             Msg other = (Msg) obj;
-            if (id != other.id)
+            if (id != other.id) {
                 return false;
+            }
             return true;
         }
     }
@@ -892,7 +893,9 @@
 
     public int setNotificationRegistration(int notificationStatus) throws RemoteException {
         // Forward the request to the MNS thread as a message - including the MAS instance ID.
-        if(D) Log.d(TAG,"setNotificationRegistration() enter");
+        if (D) {
+            Log.d(TAG, "setNotificationRegistration() enter");
+        }
         if (mMnsClient == null) {
             return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
         }
@@ -904,8 +907,8 @@
             } else {
                 //Trigger SDP Search and notificaiton registration , if SDP record not found.
                 msg.what = BluetoothMnsObexClient.MSG_MNS_SDP_SEARCH_REGISTRATION;
-                if (mMnsClient.mMnsLstRegRqst != null &&
-                        (mMnsClient.mMnsLstRegRqst.isSearchInProgress())) {
+                if (mMnsClient.mMnsLstRegRqst != null
+                        && (mMnsClient.mMnsLstRegRqst.isSearchInProgress())) {
                     /*  1. Disallow next Notification ON Request :
                      *     - Respond "Service Unavailable" as SDP Search and last notification
                      *       registration ON request is already InProgress.
@@ -928,11 +931,15 @@
             mns.sendMessageDelayed(msg, 10); // Send message without forcing a context switch
             /* Some devices - e.g. PTS needs to get the unregister confirm before we actually
              * disconnect the MNS. */
-            if(D) Log.d(TAG,"setNotificationRegistration() send : " + msg.what + " to MNS ");
+            if (D) {
+                Log.d(TAG, "setNotificationRegistration() send : " + msg.what + " to MNS ");
+            }
             return ResponseCodes.OBEX_HTTP_OK;
         } else {
             // This should not happen except at shutdown.
-            if(D) Log.d(TAG,"setNotificationRegistration() Unable to send registration request");
+            if (D) {
+                Log.d(TAG, "setNotificationRegistration() Unable to send registration request");
+            }
             return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
         }
     }
@@ -942,39 +949,40 @@
     }
 
     boolean eventMaskContainsCovo(long mask) {
-        return (sendEventConversationChanged(mask)
-                || sendEventParticipantChatstateChanged(mask));
+        return (sendEventConversationChanged(mask) || sendEventParticipantChatstateChanged(mask));
     }
 
     /* Overwrite the existing notification filter. Will register/deregister observers for
      * the Contacts and Conversation table as needed. We keep the message observer
      * at all times. */
-    /*package*/ synchronized void setNotificationFilter(long newFilter) {
+    /*package*/
+    synchronized void setNotificationFilter(long newFilter) {
         long oldFilter = mEventFilter;
         mEventFilter = newFilter;
         /* Contacts */
-        if(!eventMaskContainsContacts(oldFilter) &&
-                eventMaskContainsContacts(newFilter)) {
+        if (!eventMaskContainsContacts(oldFilter) && eventMaskContainsContacts(newFilter)) {
             // TODO:
             // Enable the observer
             // Reset the contacts list
         }
         /* Conversations */
-        if(!eventMaskContainsCovo(oldFilter) &&
-                eventMaskContainsCovo(newFilter)) {
+        if (!eventMaskContainsCovo(oldFilter) && eventMaskContainsCovo(newFilter)) {
             // TODO:
             // Enable the observer
             // Reset the conversations list
         }
     }
 
-    public void registerObserver() throws RemoteException{
-        if (V) Log.d(TAG, "registerObserver");
+    public void registerObserver() throws RemoteException {
+        if (V) {
+            Log.d(TAG, "registerObserver");
+        }
 
-        if (mObserverRegistered)
+        if (mObserverRegistered) {
             return;
+        }
 
-        if(mAccount != null) {
+        if (mAccount != null) {
 
             mProviderClient = mResolver.acquireUnstableContentProviderClient(mAuthority);
             if (mProviderClient == null) {
@@ -984,7 +992,7 @@
 
             // If there is a change in the database before we init the lists we will be sending
             // loads of events - hence init before register.
-            if(mAccount.getType() == TYPE.IM) {
+            if (mAccount.getType() == TYPE.IM) {
                 // Further add contact list tracking
                 initContactsList();
             }
@@ -994,36 +1002,44 @@
         initMsgList();
 
         /* Use MmsSms Uri since the Sms Uri is not notified on deletes */
-        if(mEnableSmsMms){
+        if (mEnableSmsMms) {
             //this is sms/mms
             mResolver.registerContentObserver(MmsSms.CONTENT_URI, false, mObserver);
             mObserverRegistered = true;
         }
 
-        if(mAccount != null) {
+        if (mAccount != null) {
             /* For URI's without account ID */
-            Uri uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
-                    + BluetoothMapContract.TABLE_MESSAGE);
-            if(D) Log.d(TAG, "Registering observer for: " + uri);
+            Uri uri = Uri.parse(
+                    mAccount.mBase_uri_no_account + "/" + BluetoothMapContract.TABLE_MESSAGE);
+            if (D) {
+                Log.d(TAG, "Registering observer for: " + uri);
+            }
             mResolver.registerContentObserver(uri, true, mObserver);
 
             /* For URI's with account ID - is handled the same way as without ID, but is
              * only triggered for MAS instances with matching account ID. */
             uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_MESSAGE);
-            if(D) Log.d(TAG, "Registering observer for: " + uri);
+            if (D) {
+                Log.d(TAG, "Registering observer for: " + uri);
+            }
             mResolver.registerContentObserver(uri, true, mObserver);
 
-            if(mAccount.getType() == TYPE.IM) {
+            if (mAccount.getType() == TYPE.IM) {
 
                 uri = Uri.parse(mAccount.mBase_uri_no_account + "/"
                         + BluetoothMapContract.TABLE_CONVOCONTACT);
-                if(D) Log.d(TAG, "Registering observer for: " + uri);
+                if (D) {
+                    Log.d(TAG, "Registering observer for: " + uri);
+                }
                 mResolver.registerContentObserver(uri, true, mObserver);
 
                 /* For URI's with account ID - is handled the same way as without ID, but is
                  * only triggered for MAS instances with matching account ID. */
                 uri = Uri.parse(mAccount.mBase_uri + "/" + BluetoothMapContract.TABLE_CONVOCONTACT);
-                if(D) Log.d(TAG, "Registering observer for: " + uri);
+                if (D) {
+                    Log.d(TAG, "Registering observer for: " + uri);
+                }
                 mResolver.registerContentObserver(uri, true, mObserver);
             }
 
@@ -1032,10 +1048,12 @@
     }
 
     public void unregisterObserver() {
-        if (V) Log.d(TAG, "unregisterObserver");
+        if (V) {
+            Log.d(TAG, "unregisterObserver");
+        }
         mResolver.unregisterContentObserver(mObserver);
         mObserverRegistered = false;
-        if(mProviderClient != null){
+        if (mProviderClient != null) {
             mProviderClient.release();
             mProviderClient = null;
         }
@@ -1057,16 +1075,16 @@
             - enable the event transmission */
         mTransmitEvents = false;
         try {
-            if(mEnableSmsMms) {
+            if (mEnableSmsMms) {
                 handleMsgListChangesSms();
                 handleMsgListChangesMms();
             }
-            if(mAccount != null) {
+            if (mAccount != null) {
                 try {
                     handleMsgListChangesMsg(mMessageUri);
                 } catch (RemoteException e) {
-                    Log.e(TAG, "Unable to update FolderVersionCounter. - Not fatal, but can cause" +
-                            " undesirable user experience!", e);
+                    Log.e(TAG, "Unable to update FolderVersionCounter. - Not fatal, but can cause"
+                            + " undesirable user experience!", e);
                 }
             }
         } finally {
@@ -1087,7 +1105,7 @@
          - enable event transmission */
         mTransmitEvents = false;
         try {
-            if((mAccount != null) && (mContactUri != null)) {
+            if ((mAccount != null) && (mContactUri != null)) {
                 handleContactListChanges(mContactUri);
             }
         } finally {
@@ -1098,16 +1116,20 @@
 
     private void sendEvent(Event evt) {
 
-        if(mTransmitEvents == false) {
-            if(V) Log.v(TAG, "mTransmitEvents == false - don't send event.");
+        if (!mTransmitEvents) {
+            if (V) {
+                Log.v(TAG, "mTransmitEvents == false - don't send event.");
+            }
             return;
         }
 
-        if(D)Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " " + evt.folder + " "
-                + evt.oldFolder + " " + evt.msgType.name() + " " + evt.datetime + " "
-                + evt.subject + " " + evt.senderName + " " + evt.priority );
+        if (D) {
+            Log.d(TAG, "sendEvent: " + evt.eventType + " " + evt.handle + " " + evt.folder + " "
+                    + evt.oldFolder + " " + evt.msgType.name() + " " + evt.datetime + " "
+                    + evt.subject + " " + evt.senderName + " " + evt.priority);
+        }
 
-        if (mMnsClient == null || mMnsClient.isConnected() == false) {
+        if (mMnsClient == null || !mMnsClient.isConnected()) {
             Log.d(TAG, "sendEvent: No MNS client registered or connected- don't send event");
             return;
         }
@@ -1118,64 +1140,88 @@
         /* This should have been a switch on the string, but it is not allowed in Java 1.6 */
         /* WARNING: Here we do pointer compare for the string to speed up things, that is.
          * HENCE: always use the EVENT_TYPE_"defines" */
-        if(evt.eventType == EVENT_TYPE_NEW) {
-            if(!sendEventNewMessage(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        if (Objects.equals(evt.eventType, EVENT_TYPE_NEW)) {
+            if (!sendEventNewMessage(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_DELETE) {
-            if(!sendEventMessageDeleted(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_DELETE)) {
+            if (!sendEventMessageDeleted(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_REMOVED) {
-            if(!sendEventMessageRemoved(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_REMOVED)) {
+            if (!sendEventMessageRemoved(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_SHIFT) {
-            if(!sendEventMessageShift(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_SHIFT)) {
+            if (!sendEventMessageShift(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_DELEVERY_SUCCESS) {
-            if(!sendEventDeliverySuccess(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_DELEVERY_SUCCESS)) {
+            if (!sendEventDeliverySuccess(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_SENDING_SUCCESS) {
-            if(!sendEventSendingSuccess(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_SENDING_SUCCESS)) {
+            if (!sendEventSendingSuccess(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_SENDING_FAILURE) {
-            if(!sendEventSendingFailed(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_SENDING_FAILURE)) {
+            if (!sendEventSendingFailed(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_DELIVERY_FAILURE) {
-            if(!sendEventDeliveryFailed(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_DELIVERY_FAILURE)) {
+            if (!sendEventDeliveryFailed(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_READ_STATUS) {
-            if(!sendEventReadStatusChanged(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_READ_STATUS)) {
+            if (!sendEventReadStatusChanged(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_CONVERSATION) {
-            if(!sendEventConversationChanged(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_CONVERSATION)) {
+            if (!sendEventConversationChanged(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_PRESENCE) {
-            if(!sendEventParticipantPresenceChanged(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_PRESENCE)) {
+            if (!sendEventParticipantPresenceChanged(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
-        } else if(evt.eventType == EVENT_TYPE_CHAT_STATE) {
-            if(!sendEventParticipantChatstateChanged(eventFilter)) {
-                if(D)Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+        } else if (Objects.equals(evt.eventType, EVENT_TYPE_CHAT_STATE)) {
+            if (!sendEventParticipantChatstateChanged(eventFilter)) {
+                if (D) {
+                    Log.d(TAG, "Skip sending event of type: " + evt.eventType);
+                }
                 return;
             }
         }
@@ -1184,22 +1230,27 @@
             mMnsClient.sendEvent(evt.encode(), mMasId);
         } catch (UnsupportedEncodingException ex) {
             /* do nothing */
-            if (D) Log.e(TAG, "Exception - should not happen: ",ex);
+            if (D) {
+                Log.e(TAG, "Exception - should not happen: ", ex);
+            }
         }
     }
 
     private void initMsgList() throws RemoteException {
-        if (V) Log.d(TAG, "initMsgList");
+        if (V) {
+            Log.d(TAG, "initMsgList");
+        }
         UserManager manager = UserManager.get(mContext);
-        if (manager == null || !manager.isUserUnlocked()) return;
+        if (manager == null || !manager.isUserUnlocked()) {
+            return;
+        }
 
         if (mEnableSmsMms) {
             HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
 
             Cursor c;
             try {
-                c = mResolver.query(Sms.CONTENT_URI,
-                    SMS_PROJECTION_SHORT, null, null, null);
+                c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
             } catch (SQLiteException e) {
                 Log.e(TAG, "Failed to initialize the list of messages: " + e.toString());
                 return;
@@ -1218,10 +1269,12 @@
                     } while (c.moveToNext());
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
 
-            synchronized(getMsgListSms()) {
+            synchronized (getMsgListSms()) {
                 getMsgListSms().clear();
                 setMsgListSms(msgListSms, true); // Set initial folder version counter
             }
@@ -1242,16 +1295,18 @@
                     } while (c.moveToNext());
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
 
-            synchronized(getMsgListMms()) {
+            synchronized (getMsgListMms()) {
                 getMsgListMms().clear();
                 setMsgListMms(msgListMms, true); // Set initial folder version counter
             }
         }
 
-        if(mAccount != null) {
+        if (mAccount != null) {
             HashMap<Long, Msg> msgList = new HashMap<Long, Msg>();
             Uri uri = mMessageUri;
             Cursor c = mProviderClient.query(uri, MSG_PROJECTION_SHORT, null, null, null);
@@ -1260,19 +1315,21 @@
                 if (c != null && c.moveToFirst()) {
                     do {
                         long id = c.getLong(c.getColumnIndex(MessageColumns._ID));
-                        long folderId = c.getInt(c.getColumnIndex(
-                                BluetoothMapContract.MessageColumns.FOLDER_ID));
-                        int readFlag = c.getInt(c.getColumnIndex(
-                                BluetoothMapContract.MessageColumns.FLAG_READ));
+                        long folderId = c.getInt(
+                                c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
+                        int readFlag = c.getInt(
+                                c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
                         Msg msg = new Msg(id, folderId, readFlag);
                         msgList.put(id, msg);
                     } while (c.moveToNext());
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
 
-            synchronized(getMsgListMsg()) {
+            synchronized (getMsgListMsg()) {
                 getMsgListMsg().clear();
                 setMsgListMsg(msgList, true);
             }
@@ -1280,15 +1337,18 @@
     }
 
     private void initContactsList() throws RemoteException {
-        if (V) Log.d(TAG, "initContactsList");
-        if(mContactUri == null) {
-            if (D) Log.d(TAG, "initContactsList() no mContactUri - nothing to init");
+        if (V) {
+            Log.d(TAG, "initContactsList");
+        }
+        if (mContactUri == null) {
+            if (D) {
+                Log.d(TAG, "initContactsList() no mContactUri - nothing to init");
+            }
             return;
         }
         Uri uri = mContactUri;
         Cursor c = mProviderClient.query(uri,
-                BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
-                null, null, null);
+                BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION, null, null, null);
         Map<String, BluetoothMapConvoContactElement> contactList =
                 new HashMap<String, BluetoothMapConvoContactElement>();
         try {
@@ -1297,9 +1357,12 @@
                 cInfo.setConvoColunms(c);
                 do {
                     long convoId = c.getLong(cInfo.mContactColConvoId);
-                    if (convoId == 0)
+                    if (convoId == 0) {
                         continue;
-                    if (V) BluetoothMapUtils.printCursor(c);
+                    }
+                    if (V) {
+                        BluetoothMapUtils.printCursor(c);
+                    }
                     String uci = c.getString(cInfo.mContactColUci);
                     String name = c.getString(cInfo.mContactColName);
                     String displayName = c.getString(cInfo.mContactColNickname);
@@ -1317,28 +1380,30 @@
                 } while (c.moveToNext());
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
-        synchronized(getContactList()) {
+        synchronized (getContactList()) {
             getContactList().clear();
             setContactList(contactList, true);
         }
     }
 
     private void handleMsgListChangesSms() {
-        if (V) Log.d(TAG, "handleMsgListChangesSms");
+        if (V) {
+            Log.d(TAG, "handleMsgListChangesSms");
+        }
 
         HashMap<Long, Msg> msgListSms = new HashMap<Long, Msg>();
         boolean listChanged = false;
 
         Cursor c;
-        synchronized(getMsgListSms()) {
+        synchronized (getMsgListSms()) {
             if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
-                c = mResolver.query(Sms.CONTENT_URI,
-                        SMS_PROJECTION_SHORT, null, null, null);
+                c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
             } else {
-                c = mResolver.query(Sms.CONTENT_URI,
-                        SMS_PROJECTION_SHORT_EXT, null, null, null);
+                c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT_EXT, null, null, null);
             }
             try {
                 if (c != null && c.moveToFirst()) {
@@ -1359,12 +1424,15 @@
                             msgListSms.put(id, msg);
                             listChanged = true;
                             Event evt;
-                            if (mTransmitEvents == true && // extract contact details only if needed
-                                    mMapEventReportVersion >
-                            BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                            if (mTransmitEvents && // extract contact details only if needed
+                                    mMapEventReportVersion
+                                            > BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
                                 String date = BluetoothMapUtils.getDateTimeString(
                                         c.getLong(c.getColumnIndex(Sms.DATE)));
                                 String subject = c.getString(c.getColumnIndex(Sms.BODY));
+                                if (subject == null) {
+                                    subject = "";
+                                }
                                 String name = "";
                                 String phone = "";
                                 if (type == 1) { //inbox
@@ -1372,39 +1440,39 @@
                                     if (phone != null && !phone.isEmpty()) {
                                         name = BluetoothMapContent.getContactNameFromPhone(phone,
                                                 mResolver);
-                                        if(name == null || name.isEmpty()){
+                                        if (name == null || name.isEmpty()) {
                                             name = phone;
                                         }
-                                    }else{
+                                    } else {
                                         name = phone;
                                     }
                                 } else {
                                     TelephonyManager tm =
-                                            (TelephonyManager)mContext.getSystemService(
-                                            Context.TELEPHONY_SERVICE);
+                                            (TelephonyManager) mContext.getSystemService(
+                                                    Context.TELEPHONY_SERVICE);
                                     if (tm != null) {
                                         phone = tm.getLine1Number();
                                         name = tm.getLine1AlphaTag();
-                                        if(name == null || name.isEmpty()){
+                                        if (name == null || name.isEmpty()) {
                                             name = phone;
                                         }
                                     }
                                 }
-                                String priority = "no";// no priority for sms
+                                String priority = "no"; // no priority for sms
                                 /* Incoming message from the network */
-                                if (mMapEventReportVersion ==
-                                        BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+                                if (mMapEventReportVersion
+                                        == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
                                     evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
                                             mSmsType, date, subject, name, priority);
                                 } else {
                                     evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
                                             mSmsType, date, subject, name, priority,
-                                            (long)threadId, null);
+                                            (long) threadId, null);
                                 }
                             } else {
                                 /* Incoming message from the network */
-                                evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type),
-                                        null, mSmsType);
+                                evt = new Event(EVENT_TYPE_NEW, id, getSmsFolderName(type), null,
+                                        mSmsType);
                             }
                             sendEvent(evt);
                         } else {
@@ -1415,19 +1483,19 @@
                                 String oldFolder = getSmsFolderName(msg.type);
                                 String newFolder = getSmsFolderName(type);
                                 // Filter out the intermediate outbox steps
-                                if(!oldFolder.equalsIgnoreCase(newFolder)) {
-                                    Event evt = new Event(EVENT_TYPE_SHIFT, id,
-                                            getSmsFolderName(type), oldFolder, mSmsType);
+                                if (!oldFolder.equalsIgnoreCase(newFolder)) {
+                                    Event evt =
+                                            new Event(EVENT_TYPE_SHIFT, id, getSmsFolderName(type),
+                                                    oldFolder, mSmsType);
                                     sendEvent(evt);
                                 }
                                 msg.type = type;
-                            } else if(threadId != msg.threadId) {
+                            } else if (threadId != msg.threadId) {
                                 listChanged = true;
-                                Log.d(TAG, "Message delete change: type: " + type
-                                        + " old type: " + msg.type
-                                        + "\n    threadId: " + threadId
+                                Log.d(TAG, "Message delete change: type: " + type + " old type: "
+                                        + msg.type + "\n    threadId: " + threadId
                                         + " old threadId: " + msg.threadId);
-                                if(threadId == DELETED_THREAD_ID) { // Message deleted
+                                if (threadId == DELETED_THREAD_ID) { // Message deleted
                                     // TODO:
                                     // We shall only use the folder attribute, but can't remember
                                     // wether to set it to "deleted" or the name of the folder
@@ -1445,11 +1513,11 @@
                                     msg.threadId = threadId;
                                 }
                             }
-                            if(read != msg.flagRead) {
+                            if (read != msg.flagRead) {
                                 listChanged = true;
                                 msg.flagRead = read;
-                                if (mMapEventReportVersion >
-                                        BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                                if (mMapEventReportVersion
+                                        > BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
                                     Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
                                             getSmsFolderName(msg.type), mSmsType);
                                     sendEvent(evt);
@@ -1460,13 +1528,15 @@
                     } while (c.moveToNext());
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
 
             for (Msg msg : getMsgListSms().values()) {
                 // "old_folder" used only for MessageShift event
-                Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
-                        getSmsFolderName(msg.type), null, mSmsType);
+                Event evt = new Event(EVENT_TYPE_DELETE, msg.id, getSmsFolderName(msg.type), null,
+                        mSmsType);
                 sendEvent(evt);
                 listChanged = true;
             }
@@ -1476,21 +1546,21 @@
     }
 
     private void handleMsgListChangesMms() {
-        if (V) Log.d(TAG, "handleMsgListChangesMms");
+        if (V) {
+            Log.d(TAG, "handleMsgListChangesMms");
+        }
 
         HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
         boolean listChanged = false;
         Cursor c;
-        synchronized(getMsgListMms()) {
+        synchronized (getMsgListMms()) {
             if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
-                c = mResolver.query(Mms.CONTENT_URI,
-                        MMS_PROJECTION_SHORT, null, null, null);
+                c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null);
             } else {
-                c = mResolver.query(Mms.CONTENT_URI,
-                        MMS_PROJECTION_SHORT_EXT, null, null, null);
+                c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT_EXT, null, null, null);
             }
 
-            try{
+            try {
                 if (c != null && c.moveToFirst()) {
                     do {
                         long id = c.getLong(c.getColumnIndex(Mms._ID));
@@ -1511,49 +1581,57 @@
                             /* New message - only notify on retrieve conf */
                             listChanged = true;
                             if (getMmsFolderName(type).equalsIgnoreCase(
-                                    BluetoothMapContract.FOLDER_NAME_INBOX) &&
-                                    mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
+                                    BluetoothMapContract.FOLDER_NAME_INBOX)
+                                    && mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
                                 continue;
                             }
                             msg = new Msg(id, type, threadId, read);
                             msgListMms.put(id, msg);
                             Event evt;
-                            if (mTransmitEvents == true && // extract contact details only if needed
-                                    mMapEventReportVersion !=
-                                    BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                            if (mTransmitEvents && // extract contact details only if needed
+                                    mMapEventReportVersion
+                                            != BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
                                 String date = BluetoothMapUtils.getDateTimeString(
                                         c.getLong(c.getColumnIndex(Mms.DATE)));
                                 String subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
                                 if (subject == null || subject.length() == 0) {
                                     /* Get subject from mms text body parts - if any exists */
                                     subject = BluetoothMapContent.getTextPartsMms(mResolver, id);
+                                    if (subject == null) {
+                                        subject = "";
+                                    }
                                 }
                                 int tmpPri = c.getInt(c.getColumnIndex(Mms.PRIORITY));
-                                Log.d(TAG, "TEMP handleMsgListChangesMms, " +
-                                        "newMessage 'read' state: " + read +
-                                        "priority: " + tmpPri);
+                                Log.d(TAG, "TEMP handleMsgListChangesMms, "
+                                        + "newMessage 'read' state: " + read + "priority: "
+                                        + tmpPri);
 
-                                String address = BluetoothMapContent.getAddressMms(
-                                        mResolver,id,BluetoothMapContent.MMS_FROM);
+                                String address = BluetoothMapContent.getAddressMms(mResolver, id,
+                                        BluetoothMapContent.MMS_FROM);
+                                if (address == null) {
+                                    address = "";
+                                }
+
                                 String priority = "no";
-                                if(tmpPri == PduHeaders.PRIORITY_HIGH)
+                                if (tmpPri == PduHeaders.PRIORITY_HIGH) {
                                     priority = "yes";
+                                }
 
                                 /* Incoming message from the network */
-                                if (mMapEventReportVersion ==
-                                        BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+                                if (mMapEventReportVersion
+                                        == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
                                     evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
                                             TYPE.MMS, date, subject, address, priority);
                                 } else {
                                     evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
                                             TYPE.MMS, date, subject, address, priority,
-                                            (long)threadId, null);
+                                            (long) threadId, null);
                                 }
 
                             } else {
                                 /* Incoming message from the network */
-                                evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type),
-                                        null, TYPE.MMS);
+                                evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type), null,
+                                        TYPE.MMS);
                             }
 
                             sendEvent(evt);
@@ -1563,7 +1641,7 @@
                                 Log.d(TAG, "new type: " + type + " old type: " + msg.type);
                                 Event evt;
                                 listChanged = true;
-                                if(msg.localInitiatedSend == false) {
+                                if (!msg.localInitiatedSend) {
                                     // Only send events about local initiated changes
                                     evt = new Event(EVENT_TYPE_SHIFT, id, getMmsFolderName(type),
                                             getMmsFolderName(msg.type), TYPE.MMS);
@@ -1573,20 +1651,19 @@
 
                                 if (getMmsFolderName(type).equalsIgnoreCase(
                                         BluetoothMapContract.FOLDER_NAME_SENT)
-                                        && msg.localInitiatedSend == true) {
+                                        && msg.localInitiatedSend) {
                                     // Stop tracking changes for this message
                                     msg.localInitiatedSend = false;
                                     evt = new Event(EVENT_TYPE_SENDING_SUCCESS, id,
                                             getMmsFolderName(type), null, TYPE.MMS);
                                     sendEvent(evt);
                                 }
-                            } else if(threadId != msg.threadId) {
+                            } else if (threadId != msg.threadId) {
                                 Log.d(TAG, "Message delete change: type: " + type + " old type: "
-                                        + msg.type
-                                        + "\n    threadId: " + threadId + " old threadId: "
-                                        + msg.threadId);
+                                        + msg.type + "\n    threadId: " + threadId
+                                        + " old threadId: " + msg.threadId);
                                 listChanged = true;
-                                if(threadId == DELETED_THREAD_ID) { // Message deleted
+                                if (threadId == DELETED_THREAD_ID) { // Message deleted
                                     // "old_folder" used only for MessageShift event
                                     Event evt = new Event(EVENT_TYPE_DELETE, id,
                                             getMmsFolderName(msg.type), null, TYPE.MMS);
@@ -1600,11 +1677,11 @@
                                     msg.threadId = threadId;
                                 }
                             }
-                            if(read != msg.flagRead) {
+                            if (read != msg.flagRead) {
                                 listChanged = true;
                                 msg.flagRead = read;
-                                if (mMapEventReportVersion >
-                                        BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                                if (mMapEventReportVersion
+                                        > BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
                                     Event evt = new Event(EVENT_TYPE_READ_STATUS, id,
                                             getMmsFolderName(msg.type), TYPE.MMS);
                                     sendEvent(evt);
@@ -1616,12 +1693,14 @@
 
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
             for (Msg msg : getMsgListMms().values()) {
                 // "old_folder" used only for MessageShift event
-                Event evt = new Event(EVENT_TYPE_DELETE, msg.id,
-                        getMmsFolderName(msg.type), null, TYPE.MMS);
+                Event evt = new Event(EVENT_TYPE_DELETE, msg.id, getMmsFolderName(msg.type), null,
+                        TYPE.MMS);
                 sendEvent(evt);
                 listChanged = true;
             }
@@ -1629,8 +1708,10 @@
         }
     }
 
-    private void handleMsgListChangesMsg(Uri uri)  throws RemoteException{
-        if (V) Log.v(TAG, "handleMsgListChangesMsg uri: " + uri.toString());
+    private void handleMsgListChangesMsg(Uri uri) throws RemoteException {
+        if (V) {
+            Log.v(TAG, "handleMsgListChangesMsg uri: " + uri.toString());
+        }
 
         // TODO: Change observer to handle accountId and message ID if present
 
@@ -1644,20 +1725,20 @@
         } else {
             c = mProviderClient.query(mMessageUri, MSG_PROJECTION_SHORT_EXT2, null, null, null);
         }
-        synchronized(getMsgListMsg()) {
+        synchronized (getMsgListMsg()) {
             try {
                 if (c != null && c.moveToFirst()) {
                     do {
-                        long id = c.getLong(c.getColumnIndex(
-                                BluetoothMapContract.MessageColumns._ID));
-                        int folderId = c.getInt(c.getColumnIndex(
-                                BluetoothMapContract.MessageColumns.FOLDER_ID));
-                        int readFlag = c.getInt(c.getColumnIndex(
-                                BluetoothMapContract.MessageColumns.FLAG_READ));
+                        long id = c.getLong(
+                                c.getColumnIndex(BluetoothMapContract.MessageColumns._ID));
+                        int folderId = c.getInt(
+                                c.getColumnIndex(BluetoothMapContract.MessageColumns.FOLDER_ID));
+                        int readFlag = c.getInt(
+                                c.getColumnIndex(BluetoothMapContract.MessageColumns.FLAG_READ));
                         Msg msg = getMsgListMsg().remove(id);
                         BluetoothMapFolderElement folderElement = mFolders.getFolderById(folderId);
                         String newFolder;
-                        if(folderElement != null) {
+                        if (folderElement != null) {
                             newFolder = folderElement.getFullPath();
                         } else {
                             // This can happen if a new folder is created while connected
@@ -1673,30 +1754,31 @@
                             Event evt;
                             /* Incoming message from the network */
                             if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
-                                String date = BluetoothMapUtils.getDateTimeString(
-                                        c.getLong(c.getColumnIndex(
+                                String date = BluetoothMapUtils.getDateTimeString(c.getLong(
+                                        c.getColumnIndex(
                                                 BluetoothMapContract.MessageColumns.DATE)));
                                 String subject = c.getString(c.getColumnIndex(
                                         BluetoothMapContract.MessageColumns.SUBJECT));
                                 String address = c.getString(c.getColumnIndex(
                                         BluetoothMapContract.MessageColumns.FROM_LIST));
                                 String priority = "no";
-                                if(c.getInt(c.getColumnIndex(
+                                if (c.getInt(c.getColumnIndex(
                                         BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY))
-                                        == 1)
+                                        == 1) {
                                     priority = "yes";
-                                if (mMapEventReportVersion ==
-                                        BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
+                                }
+                                if (mMapEventReportVersion
+                                        == BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
                                     evt = new Event(EVENT_TYPE_NEW, id, newFolder,
                                             mAccount.getType(), date, subject, address, priority);
                                 } else {
-                                    long thread_id = c.getLong(c.getColumnIndex(
+                                    long threadId = c.getLong(c.getColumnIndex(
                                             BluetoothMapContract.MessageColumns.THREAD_ID));
-                                    String thread_name = c.getString(c.getColumnIndex(
+                                    String threadName = c.getString(c.getColumnIndex(
                                             BluetoothMapContract.MessageColumns.THREAD_NAME));
                                     evt = new Event(EVENT_TYPE_NEW, id, newFolder,
                                             mAccount.getType(), date, subject, address, priority,
-                                            thread_id, thread_name);
+                                            threadId, threadName);
                                 }
                             } else {
                                 evt = new Event(EVENT_TYPE_NEW, id, newFolder, null, TYPE.EMAIL);
@@ -1705,42 +1787,43 @@
                         } else {
                             /* Existing message */
                             if (folderId != msg.folderId && msg.folderId != -1) {
-                                if (D) Log.d(TAG, "new folderId: " + folderId + " old folderId: "
-                                        + msg.folderId);
+                                if (D) {
+                                    Log.d(TAG, "new folderId: " + folderId + " old folderId: "
+                                            + msg.folderId);
+                                }
                                 BluetoothMapFolderElement oldFolderElement =
                                         mFolders.getFolderById(msg.folderId);
                                 String oldFolder;
                                 listChanged = true;
-                                if(oldFolderElement != null) {
+                                if (oldFolderElement != null) {
                                     oldFolder = oldFolderElement.getFullPath();
                                 } else {
                                     // This can happen if a new folder is created while connected
                                     oldFolder = "unknown";
                                 }
-                                BluetoothMapFolderElement deletedFolder =
-                                        mFolders.getFolderByName(
-                                                BluetoothMapContract.FOLDER_NAME_DELETED);
-                                BluetoothMapFolderElement sentFolder =
-                                        mFolders.getFolderByName(
-                                                BluetoothMapContract.FOLDER_NAME_SENT);
+                                BluetoothMapFolderElement deletedFolder = mFolders.getFolderByName(
+                                        BluetoothMapContract.FOLDER_NAME_DELETED);
+                                BluetoothMapFolderElement sentFolder = mFolders.getFolderByName(
+                                        BluetoothMapContract.FOLDER_NAME_SENT);
                                 /*
                                  *  If the folder is now 'deleted', send a deleted-event in stead of
                                  *  a shift or if message is sent initiated by MAP Client, then send
                                  *  sending-success otherwise send folderShift
                                  */
-                                if(deletedFolder != null && deletedFolder.getFolderId()
-                                        == folderId) {
+                                if (deletedFolder != null
+                                        && deletedFolder.getFolderId() == folderId) {
                                     // "old_folder" used only for MessageShift event
-                                    Event evt = new Event(EVENT_TYPE_DELETE, msg.id, oldFolder,
-                                            null, mAccount.getType());
+                                    Event evt =
+                                            new Event(EVENT_TYPE_DELETE, msg.id, oldFolder, null,
+                                                    mAccount.getType());
                                     sendEvent(evt);
-                                } else if(sentFolder != null
+                                } else if (sentFolder != null
                                         && sentFolder.getFolderId() == folderId
-                                        && msg.localInitiatedSend == true) {
-                                    if(msg.transparent) {
+                                        && msg.localInitiatedSend) {
+                                    if (msg.transparent) {
                                         mResolver.delete(
-                                                ContentUris.withAppendedId(mMessageUri, id),
-                                                null, null);
+                                                ContentUris.withAppendedId(mMessageUri, id), null,
+                                                null);
                                     } else {
                                         msg.localInitiatedSend = false;
                                         Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id,
@@ -1756,11 +1839,11 @@
                                 }
                                 msg.folderId = folderId;
                             }
-                            if(readFlag != msg.flagRead) {
+                            if (readFlag != msg.flagRead) {
                                 listChanged = true;
 
-                                if (mMapEventReportVersion >
-                                BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
+                                if (mMapEventReportVersion
+                                        > BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
                                     Event evt = new Event(EVENT_TYPE_READ_STATUS, id, newFolder,
                                             mAccount.getType());
                                     sendEvent(evt);
@@ -1773,14 +1856,16 @@
                     } while (c.moveToNext());
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
             // For all messages no longer in the database send a delete notification
             for (Msg msg : getMsgListMsg().values()) {
                 BluetoothMapFolderElement oldFolderElement = mFolders.getFolderById(msg.folderId);
                 String oldFolder;
                 listChanged = true;
-                if(oldFolderElement != null) {
+                if (oldFolderElement != null) {
                     oldFolder = oldFolderElement.getFullPath();
                 } else {
                     oldFolder = "unknown";
@@ -1789,11 +1874,12 @@
                  * new message in sent. We cannot track the message anymore, hence send both a
                  * send success and delete message.
                  */
-                if(msg.localInitiatedSend == true) {
+                if (msg.localInitiatedSend) {
                     msg.localInitiatedSend = false;
                     // If message is send with transparency don't set folder as message is deleted
-                    if (msg.transparent)
+                    if (msg.transparent) {
                         oldFolder = null;
+                    }
                     Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, msg.id, oldFolder, null,
                             mAccount.getType());
                     sendEvent(evt);
@@ -1804,8 +1890,8 @@
                 if (!msg.transparent) {
 
                     // "old_folder" used only for MessageShift event
-                    Event evt = new Event(EVENT_TYPE_DELETE, msg.id, oldFolder,
-                            null, mAccount.getType());
+                    Event evt = new Event(EVENT_TYPE_DELETE, msg.id, oldFolder, null,
+                            mAccount.getType());
                     sendEvent(evt);
                 }
             }
@@ -1814,15 +1900,17 @@
     }
 
     private void handleMsgListChanges(Uri uri) {
-        if(uri.getAuthority().equals(mAuthority)) {
+        if (uri.getAuthority().equals(mAuthority)) {
             try {
-                if(D) Log.d(TAG, "handleMsgListChanges: account type = "
-                        + mAccount.getType().toString());
+                if (D) {
+                    Log.d(TAG, "handleMsgListChanges: account type = " + mAccount.getType()
+                            .toString());
+                }
                 handleMsgListChangesMsg(uri);
-            } catch(RemoteException e) {
+            } catch (RemoteException e) {
                 mMasInstance.restartObexServerSession();
-                Log.w(TAG, "Problems contacting the ContentProvider in mas Instance "
-                        + mMasId + " restaring ObexServerSession");
+                Log.w(TAG, "Problems contacting the ContentProvider in mas Instance " + mMasId
+                        + " restaring ObexServerSession");
             }
 
         }
@@ -1836,7 +1924,9 @@
     private void handleContactListChanges(Uri uri) {
         if (uri.getAuthority().equals(mAuthority)) {
             try {
-                if (V) Log.v(TAG,"handleContactListChanges uri: " + uri.toString());
+                if (V) {
+                    Log.v(TAG, "handleContactListChanges uri: " + uri.toString());
+                }
                 Cursor c = null;
                 boolean listChanged = false;
                 try {
@@ -1844,31 +1934,34 @@
 
                     if (mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V10
                             && mMapEventReportVersion != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
-                        c = mProviderClient
-                                .query(mContactUri,
-                                        BluetoothMapContract.
-                                        BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
-                                        null, null, null);
+                        c = mProviderClient.query(mContactUri,
+                                BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION,
+                                null, null, null);
                         cInfo.setConvoColunms(c);
                     } else {
-                        if (V) Log.v(TAG,"handleContactListChanges MAP version does not" +
-                                "support convocontact notifications");
+                        if (V) {
+                            Log.v(TAG, "handleContactListChanges MAP version does not"
+                                    + "support convocontact notifications");
+                        }
                         return;
                     }
 
                     HashMap<String, BluetoothMapConvoContactElement> contactList =
-                            new HashMap<String,
-                            BluetoothMapConvoContactElement>(getContactList().size());
+                            new HashMap<String, BluetoothMapConvoContactElement>(
+                                    getContactList().size());
 
                     synchronized (getContactList()) {
                         if (c != null && c.moveToFirst()) {
                             do {
                                 String uci = c.getString(cInfo.mContactColUci);
                                 long convoId = c.getLong(cInfo.mContactColConvoId);
-                                if (convoId == 0)
+                                if (convoId == 0) {
                                     continue;
+                                }
 
-                                if (V) BluetoothMapUtils.printCursor(c);
+                                if (V) {
+                                    BluetoothMapUtils.printCursor(c);
+                                }
 
                                 BluetoothMapConvoContactElement contact =
                                         getContactList().remove(uci);
@@ -1890,34 +1983,30 @@
                                             && mMapEventReportVersion
                                             != BluetoothMapUtils.MAP_EVENT_REPORT_V11) {
                                         Event evt;
-                                        String name = c
-                                                .getString(cInfo.mContactColName);
-                                        String displayName = c
-                                                .getString(cInfo.mContactColNickname);
-                                        String presenceStatus = c
-                                                .getString(cInfo.mContactColPresenceText);
-                                        int presenceState = c
-                                                .getInt(cInfo.mContactColPresenceState);
-                                        long lastActivity = c
-                                                .getLong(cInfo.mContactColLastActive);
-                                        int chatState = c
-                                                .getInt(cInfo.mContactColChatState);
-                                        int priority = c
-                                                .getInt(cInfo.mContactColPriority);
-                                        String btUid = c
-                                                .getString(cInfo.mContactColBtUid);
+                                        String name = c.getString(cInfo.mContactColName);
+                                        String displayName = c.getString(cInfo.mContactColNickname);
+                                        String presenceStatus =
+                                                c.getString(cInfo.mContactColPresenceText);
+                                        int presenceState =
+                                                c.getInt(cInfo.mContactColPresenceState);
+                                        long lastActivity = c.getLong(cInfo.mContactColLastActive);
+                                        int chatState = c.getInt(cInfo.mContactColChatState);
+                                        int priority = c.getInt(cInfo.mContactColPriority);
+                                        String btUid = c.getString(cInfo.mContactColBtUid);
 
                                         // Get Conversation information for
                                         // event
 //                                        Uri convoUri = Uri
 //                                                .parse(mAccount.mBase_uri
 //                                                        + "/"
-//                                                        + BluetoothMapContract.TABLE_CONVERSATION);
+//                                                        + BluetoothMapContract
+// .TABLE_CONVERSATION);
 //                                        String whereClause = "contacts._id = "
 //                                                + convoId;
 //                                        Cursor cConvo = mProviderClient
 //                                                .query(convoUri,
-//                                                       BluetoothMapContract.BT_CONVERSATION_PROJECTION,
+//                                                       BluetoothMapContract
+// .BT_CONVERSATION_PROJECTION,
 //                                                       whereClause, null, null);
                                         // TODO: will move out of the loop when merged with CB's
                                         // changes make sure to look up col index out side loop
@@ -1926,27 +2015,20 @@
 //                                                && cConvo.moveToFirst()) {
 //                                            convoName = cConvo
 //                                                    .getString(cConvo
-//                                                            .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
+//                                                            .getColumnIndex
+// (BluetoothMapContract.ConvoContactColumns.NAME));
 //                                        }
 
-                                        contact = new BluetoothMapConvoContactElement(
-                                                uci, name, displayName,
-                                                presenceStatus, presenceState,
-                                                lastActivity, chatState,
-                                                priority, btUid);
+                                        contact = new BluetoothMapConvoContactElement(uci, name,
+                                                displayName, presenceStatus, presenceState,
+                                                lastActivity, chatState, priority, btUid);
 
                                         contactList.put(uci, contact);
 
-                                        evt = new Event(
-                                                EVENT_TYPE_CONVERSATION,
-                                                uci,
-                                                mAccount.getType(),
-                                                name,
-                                                String.valueOf(priority),
-                                                BluetoothMapUtils
-                                                .getDateTimeString(lastActivity),
-                                                convoId, convoName,
-                                                presenceState, presenceStatus,
+                                        evt = new Event(EVENT_TYPE_CONVERSATION, uci,
+                                                mAccount.getType(), name, String.valueOf(priority),
+                                                BluetoothMapUtils.getDateTimeString(lastActivity),
+                                                convoId, convoName, presenceState, presenceStatus,
                                                 chatState);
 
                                         sendEvent(evt);
@@ -1963,7 +2045,8 @@
 //                                            + convoId;
 //                                    Cursor cConvo = mProviderClient
 //                                            .query(convoUri,
-//                                                    BluetoothMapContract.BT_CONVERSATION_PROJECTION,
+//                                                    BluetoothMapContract
+// .BT_CONVERSATION_PROJECTION,
 //                                                    whereClause, null, null);
 //                                    // TODO: will move out of the loop when merged with CB's
 //                                    // changes make sure to look up col index out side loop
@@ -1971,38 +2054,31 @@
 //                                    if (cConvo != null && cConvo.moveToFirst()) {
 //                                        convoName = cConvo
 //                                                .getString(cConvo
-//                                                        .getColumnIndex(BluetoothMapContract.ConvoContactColumns.NAME));
+//                                                        .getColumnIndex(BluetoothMapContract
+// .ConvoContactColumns.NAME));
 //                                    }
 
                                     // Check if presence is updated
                                     int presenceState = c.getInt(cInfo.mContactColPresenceState);
-                                    String presenceStatus = c.getString(
-                                            cInfo.mContactColPresenceText);
-                                    String currentPresenceStatus = contact
-                                            .getPresenceStatus();
+                                    String presenceStatus =
+                                            c.getString(cInfo.mContactColPresenceText);
+                                    String currentPresenceStatus = contact.getPresenceStatus();
                                     if (contact.getPresenceAvailability() != presenceState
-                                            || currentPresenceStatus != presenceStatus) {
-                                        long lastOnline = c
-                                                .getLong(cInfo.mContactColLastOnline);
+                                            || !Objects.equals(currentPresenceStatus,
+                                            presenceStatus)) {
+                                        long lastOnline = c.getLong(cInfo.mContactColLastOnline);
                                         contact.setPresenceAvailability(presenceState);
                                         contact.setLastActivity(lastOnline);
                                         if (currentPresenceStatus != null
-                                                && !currentPresenceStatus
-                                                .equals(presenceStatus)) {
+                                                && !currentPresenceStatus.equals(presenceStatus)) {
                                             contact.setPresenceStatus(presenceStatus);
                                         }
-                                        Event evt = new Event(
-                                                EVENT_TYPE_PRESENCE,
-                                                uci,
-                                                mAccount.getType(),
-                                                contact.getName(),
-                                                String.valueOf(contact
-                                                        .getPriority()),
-                                                        BluetoothMapUtils
-                                                        .getDateTimeString(lastOnline),
-                                                        convoId, convoName,
-                                                        presenceState, presenceStatus,
-                                                        0);
+                                        Event evt = new Event(EVENT_TYPE_PRESENCE, uci,
+                                                mAccount.getType(), contact.getName(),
+                                                String.valueOf(contact.getPriority()),
+                                                BluetoothMapUtils.getDateTimeString(lastOnline),
+                                                convoId, convoName, presenceState, presenceStatus,
+                                                0);
                                         sendEvent(evt);
                                     }
 
@@ -2013,24 +2089,18 @@
                                         long lastActivity = c.getLong(cInfo.mContactColLastActive);
                                         contact.setLastActivity(lastActivity);
                                         contact.setChatState(chatState);
-                                        Event evt = new Event(
-                                                EVENT_TYPE_CHAT_STATE,
-                                                uci,
-                                                mAccount.getType(),
-                                                contact.getName(),
-                                                String.valueOf(contact
-                                                        .getPriority()),
-                                                        BluetoothMapUtils
-                                                        .getDateTimeString(lastActivity),
-                                                        convoId, convoName, 0, null,
-                                                        chatState);
+                                        Event evt = new Event(EVENT_TYPE_CHAT_STATE, uci,
+                                                mAccount.getType(), contact.getName(),
+                                                String.valueOf(contact.getPriority()),
+                                                BluetoothMapUtils.getDateTimeString(lastActivity),
+                                                convoId, convoName, 0, null, chatState);
                                         sendEvent(evt);
                                     }
                                     contactList.put(uci, contact);
                                 }
                             } while (c.moveToNext());
                         }
-                        if(getContactList().size() > 0) {
+                        if (getContactList().size() > 0) {
                             // one or more contacts were deleted, hence the conversation listing
                             // version counter should change.
                             listChanged = true;
@@ -2038,13 +2108,14 @@
                         setContactList(contactList, listChanged);
                     } // end synchronized
                 } finally {
-                    if (c != null) c.close();
+                    if (c != null) {
+                        c.close();
+                    }
                 }
             } catch (RemoteException e) {
                 mMasInstance.restartObexServerSession();
-                Log.w(TAG,
-                        "Problems contacting the ContentProvider in mas Instance "
-                                + mMasId + " restaring ObexServerSession");
+                Log.w(TAG, "Problems contacting the ContentProvider in mas Instance " + mMasId
+                        + " restaring ObexServerSession");
             }
 
         }
@@ -2058,22 +2129,22 @@
 
         int updateCount = 0;
         ContentValues contentValues = new ContentValues();
-        BluetoothMapFolderElement deleteFolder = mFolders.
-                getFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
+        BluetoothMapFolderElement deleteFolder =
+                mFolders.getFolderByName(BluetoothMapContract.FOLDER_NAME_DELETED);
         contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
-        synchronized(getMsgListMsg()) {
+        synchronized (getMsgListMsg()) {
             Msg msg = getMsgListMsg().get(handle);
             if (status == BluetoothMapAppParams.STATUS_VALUE_YES) {
                 /* Set deleted folder id */
                 long folderId = -1;
-                if(deleteFolder != null) {
+                if (deleteFolder != null) {
                     folderId = deleteFolder.getFolderId();
                 }
-                contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID,folderId);
+                contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
                 updateCount = mResolver.update(uri, contentValues, null, null);
                 /* The race between updating the value in our cached values and the database
                  * is handled by the synchronized statement. */
-                if(updateCount > 0) {
+                if (updateCount > 0) {
                     res = true;
                     if (msg != null) {
                         msg.oldFolderId = msg.folderId;
@@ -2081,7 +2152,9 @@
                          * initiated actions. */
                         msg.folderId = folderId;
                     }
-                    if(D) Log.d(TAG, "Deleted MSG: " + handle + " from folderId: " + folderId);
+                    if (D) {
+                        Log.d(TAG, "Deleted MSG: " + handle + " from folderId: " + folderId);
+                    }
                 } else {
                     Log.w(TAG, "Msg: " + handle + " - Set delete status " + status
                             + " failed for folderId " + folderId);
@@ -2089,24 +2162,26 @@
             } else if (status == BluetoothMapAppParams.STATUS_VALUE_NO) {
                 /* Undelete message. move to old folder if we know it,
                  * else move to inbox - as dictated by the spec. */
-                if(msg != null && deleteFolder != null &&
-                        msg.folderId == deleteFolder.getFolderId()) {
+                if (msg != null && deleteFolder != null
+                        && msg.folderId == deleteFolder.getFolderId()) {
                     /* Only modify messages in the 'Deleted' folder */
                     long folderId = -1;
-                    BluetoothMapFolderElement inboxFolder = mCurrentFolder.
-                            getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
+                    BluetoothMapFolderElement inboxFolder =
+                            mCurrentFolder.getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
                     if (msg != null && msg.oldFolderId != -1) {
                         folderId = msg.oldFolderId;
                     } else {
-                        if(inboxFolder != null) {
+                        if (inboxFolder != null) {
                             folderId = inboxFolder.getFolderId();
                         }
-                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
-                                "is unknown. Moving to inbox.");
+                        if (D) {
+                            Log.d(TAG, "We did not delete the message, hence the old folder "
+                                    + "is unknown. Moving to inbox.");
+                        }
                     }
                     contentValues.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
                     updateCount = mResolver.update(uri, contentValues, null, null);
-                    if(updateCount > 0) {
+                    if (updateCount > 0) {
                         res = true;
                         /* Update the folder ID to avoid triggering an event for MCE
                          * initiated actions. */
@@ -2114,31 +2189,33 @@
                          * message to INBOX - clearified in errata 5591.
                          * Therefore we update the cache to INBOX-folderId - to trigger a message
                          * shift event to the old-folder. */
-                        if(inboxFolder != null) {
+                        if (inboxFolder != null) {
                             msg.folderId = inboxFolder.getFolderId();
                         } else {
                             msg.folderId = folderId;
                         }
                     } else {
-                        if(D)Log.d(TAG,"We did not delete the message, hence the old folder " +
-                                "is unknown. Moving to inbox.");
+                        if (D) {
+                            Log.d(TAG, "We did not delete the message, hence the old folder "
+                                    + "is unknown. Moving to inbox.");
+                        }
                     }
                 }
             }
-            if(V) {
+            if (V) {
                 BluetoothMapFolderElement folderElement;
                 String folderName = "unknown";
                 if (msg != null) {
                     folderElement = mCurrentFolder.getFolderById(msg.folderId);
-                    if(folderElement != null) {
+                    if (folderElement != null) {
                         folderName = folderElement.getName();
                     }
                 }
-                Log.d(TAG,"setEmailMessageStatusDelete: " + handle + " from " + folderName
+                Log.d(TAG, "setEmailMessageStatusDelete: " + handle + " from " + folderName
                         + " status: " + status);
             }
         }
-        if(res == false) {
+        if (!res) {
             Log.w(TAG, "Set delete status " + status + " failed.");
         }
         return res;
@@ -2160,16 +2237,16 @@
                 int threadId = c.getInt(c.getColumnIndex(Mms.THREAD_ID));
                 if (threadId != DELETED_THREAD_ID) {
                     /* Set deleted thread id */
-                    synchronized(getMsgListMms()) {
+                    synchronized (getMsgListMms()) {
                         Msg msg = getMsgListMms().get(handle);
-                        if(msg != null) { // This will always be the case
+                        if (msg != null) { // This will always be the case
                             msg.threadId = DELETED_THREAD_ID;
                         }
                     }
                     updateThreadId(uri, Mms.THREAD_ID, DELETED_THREAD_ID);
                 } else {
                     /* Delete from observer message list to avoid delete notifications */
-                    synchronized(getMsgListMms()) {
+                    synchronized (getMsgListMms()) {
                         getMsgListMms().remove(handle);
                     }
                     /* Delete message */
@@ -2178,7 +2255,9 @@
                 res = true;
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
 
         return res;
@@ -2207,9 +2286,9 @@
                     Set<String> recipients = new HashSet<String>();
                     recipients.addAll(Arrays.asList(address));
                     Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
-                    synchronized(getMsgListMms()) {
+                    synchronized (getMsgListMms()) {
                         Msg msg = getMsgListMms().get(handle);
-                        if(msg != null) { // This will always be the case
+                        if (msg != null) { // This will always be the case
                             msg.threadId = oldThreadId.intValue();
                             // Spec. states that undelete shall shift the message to Inbox.
                             // Hence we need to trigger a message shift from INBOX to old-folder
@@ -2222,13 +2301,15 @@
                     }
                     updateThreadId(uri, Mms.THREAD_ID, oldThreadId);
                 } else {
-                    Log.d(TAG, "Message not in deleted folder: handle " + handle
-                            + " threadId " + threadId);
+                    Log.d(TAG, "Message not in deleted folder: handle " + handle + " threadId "
+                            + threadId);
                 }
                 res = true;
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
         return res;
     }
@@ -2242,9 +2323,9 @@
                 /* Move to deleted folder, or delete if already in deleted folder */
                 int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
                 if (threadId != DELETED_THREAD_ID) {
-                    synchronized(getMsgListSms()) {
+                    synchronized (getMsgListSms()) {
                         Msg msg = getMsgListSms().get(handle);
-                        if(msg != null) { // This will always be the case
+                        if (msg != null) { // This will always be the case
                             msg.threadId = DELETED_THREAD_ID;
                         }
                     }
@@ -2252,7 +2333,7 @@
                     updateThreadId(uri, Sms.THREAD_ID, DELETED_THREAD_ID);
                 } else {
                     /* Delete from observer message list to avoid delete notifications */
-                    synchronized(getMsgListSms()) {
+                    synchronized (getMsgListSms()) {
                         getMsgListSms().remove(handle);
                     }
                     /* Delete message */
@@ -2261,7 +2342,9 @@
                 res = true;
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
         return res;
     }
@@ -2278,9 +2361,9 @@
                     Set<String> recipients = new HashSet<String>();
                     recipients.addAll(Arrays.asList(address));
                     Long oldThreadId = Telephony.Threads.getOrCreateThreadId(mContext, recipients);
-                    synchronized(getMsgListSms()) {
+                    synchronized (getMsgListSms()) {
                         Msg msg = getMsgListSms().get(handle);
-                        if(msg != null) {
+                        if (msg != null) {
                             msg.threadId = oldThreadId.intValue();
                             /* This will always be the case
                              * The threadId is specified as an int, so it is safe to truncate
@@ -2299,13 +2382,15 @@
                     }
                     updateThreadId(uri, Sms.THREAD_ID, oldThreadId);
                 } else {
-                    Log.d(TAG, "Message not in deleted folder: handle " + handle
-                            + " threadId " + threadId);
+                    Log.d(TAG, "Message not in deleted folder: handle " + handle + " threadId "
+                            + threadId);
                 }
                 res = true;
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
         return res;
     }
@@ -2322,14 +2407,18 @@
     public boolean setMessageStatusDeleted(long handle, TYPE type,
             BluetoothMapFolderElement mCurrentFolder, String uriStr, int statusValue) {
         boolean res = false;
-        if (D) Log.d(TAG, "setMessageStatusDeleted: handle " + handle
-                + " type " + type + " value " + statusValue);
+        if (D) {
+            Log.d(TAG, "setMessageStatusDeleted: handle " + handle + " type " + type + " value "
+                    + statusValue);
+        }
 
         if (type == TYPE.EMAIL) {
             res = setEmailMessageStatusDelete(mCurrentFolder, uriStr, handle, statusValue);
         } else if (type == TYPE.IM) {
             // TODO: to do when deleting IM message
-            if (D) Log.d(TAG, "setMessageStatusDeleted: IM not handled" );
+            if (D) {
+                Log.d(TAG, "setMessageStatusDeleted: IM not handled");
+            }
         } else {
             if (statusValue == BluetoothMapAppParams.STATUS_VALUE_YES) {
                 if (type == TYPE.SMS_GSM || type == TYPE.SMS_CDMA) {
@@ -2357,11 +2446,13 @@
      * @return true at success
      */
     public boolean setMessageStatusRead(long handle, TYPE type, String uriStr, int statusValue)
-            throws RemoteException{
+            throws RemoteException {
         int count = 0;
 
-        if (D) Log.d(TAG, "setMessageStatusRead: handle " + handle
-                + " type " + type + " value " + statusValue);
+        if (D) {
+            Log.d(TAG, "setMessageStatusRead: handle " + handle + " type " + type + " value "
+                    + statusValue);
+        }
 
         /* Approved MAP spec errata 3445 states that read status initiated
          * by the MCE shall change the MSE read status. */
@@ -2371,38 +2462,45 @@
             contentValues.put(Sms.READ, statusValue);
             contentValues.put(Sms.SEEN, statusValue);
             String values = contentValues.toString();
-            if (D) Log.d(TAG, " -> SMS Uri: " + uri.toString() + " values " + values);
-            synchronized(getMsgListSms()) {
+            if (D) {
+                Log.d(TAG, " -> SMS Uri: " + uri.toString() + " values " + values);
+            }
+            synchronized (getMsgListSms()) {
                 Msg msg = getMsgListSms().get(handle);
-                if(msg != null) { // This will always be the case
+                if (msg != null) { // This will always be the case
                     msg.flagRead = statusValue;
                 }
             }
             count = mResolver.update(uri, contentValues, null, null);
-            if (D) Log.d(TAG, " -> "+count +" rows updated!");
+            if (D) {
+                Log.d(TAG, " -> " + count + " rows updated!");
+            }
 
         } else if (type == TYPE.MMS) {
             Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
-            if (D) Log.d(TAG, " -> MMS Uri: " + uri.toString());
+            if (D) {
+                Log.d(TAG, " -> MMS Uri: " + uri.toString());
+            }
             ContentValues contentValues = new ContentValues();
             contentValues.put(Mms.READ, statusValue);
-            synchronized(getMsgListMms()) {
+            synchronized (getMsgListMms()) {
                 Msg msg = getMsgListMms().get(handle);
-                if(msg != null) { // This will always be the case
+                if (msg != null) { // This will always be the case
                     msg.flagRead = statusValue;
                 }
             }
             count = mResolver.update(uri, contentValues, null, null);
-            if (D) Log.d(TAG, " -> "+count +" rows updated!");
-        } else if (type == TYPE.EMAIL ||
-                type == TYPE.IM) {
+            if (D) {
+                Log.d(TAG, " -> " + count + " rows updated!");
+            }
+        } else if (type == TYPE.EMAIL || type == TYPE.IM) {
             Uri uri = mMessageUri;
             ContentValues contentValues = new ContentValues();
             contentValues.put(BluetoothMapContract.MessageColumns.FLAG_READ, statusValue);
             contentValues.put(BluetoothMapContract.MessageColumns._ID, handle);
-            synchronized(getMsgListMsg()) {
+            synchronized (getMsgListMsg()) {
                 Msg msg = getMsgListMsg().get(handle);
-                if(msg != null) { // This will always be the case
+                if (msg != null) { // This will always be the case
                     msg.flagRead = statusValue;
                 }
             }
@@ -2413,22 +2511,21 @@
     }
 
     private class PushMsgInfo {
-        long id;
-        int transparent;
-        int retry;
-        String phone;
-        Uri uri;
-        long timestamp;
-        int parts;
-        int partsSent;
-        int partsDelivered;
-        boolean resend;
-        boolean sendInProgress;
-        boolean failedSent; // Set to true if a single part sent fail is received.
-        int statusDelivered; // Set to != 0 if a single part deliver fail is received.
+        public long id;
+        public int transparent;
+        public int retry;
+        public String phone;
+        public Uri uri;
+        public long timestamp;
+        public int parts;
+        public int partsSent;
+        public int partsDelivered;
+        public boolean resend;
+        public boolean sendInProgress;
+        public boolean failedSent; // Set to true if a single part sent fail is received.
+        public int statusDelivered; // Set to != 0 if a single part deliver fail is received.
 
-        public PushMsgInfo(long id, int transparent,
-                int retry, String phone, Uri uri) {
+        PushMsgInfo(long id, int transparent, int retry, String phone, Uri uri) {
             this.id = id;
             this.transparent = transparent;
             this.retry = retry;
@@ -2439,7 +2536,9 @@
             this.failedSent = false;
             this.statusDelivered = 0; /* Assume success */
             this.timestamp = 0;
-        };
+        }
+
+        ;
     }
 
     private Map<Long, PushMsgInfo> mPushMsgList =
@@ -2447,11 +2546,13 @@
 
     public long pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement,
             BluetoothMapAppParams ap, String emailBaseUri)
-                    throws IllegalArgumentException, RemoteException, IOException {
-        if (D) Log.d(TAG, "pushMessage");
-        ArrayList<BluetoothMapbMessage.vCard> recipientList = msg.getRecipients();
-        int transparent = (ap.getTransparent() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ?
-                0 : ap.getTransparent();
+            throws IllegalArgumentException, RemoteException, IOException {
+        if (D) {
+            Log.d(TAG, "pushMessage");
+        }
+        ArrayList<BluetoothMapbMessage.VCard> recipientList = msg.getRecipients();
+        int transparent = (ap.getTransparent() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? 0
+                : ap.getTransparent();
         int retry = ap.getRetry();
         int charset = ap.getCharset();
         long handle = -1;
@@ -2459,9 +2560,9 @@
 
         if (recipientList == null) {
             if (folderElement.getName().equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
-                BluetoothMapbMessage.vCard empty =
-                    new BluetoothMapbMessage.vCard("", "", null, null, 0);
-                recipientList = new ArrayList<BluetoothMapbMessage.vCard>();
+                BluetoothMapbMessage.VCard empty =
+                        new BluetoothMapbMessage.VCard("", "", null, null, 0);
+                recipientList = new ArrayList<BluetoothMapbMessage.VCard>();
                 recipientList.add(empty);
                 Log.w(TAG, "Added empty recipient to draft message");
             } else {
@@ -2470,32 +2571,36 @@
             }
         }
 
-        if ( msg.getType().equals(TYPE.EMAIL) ) {
+        if (msg.getType().equals(TYPE.EMAIL)) {
             /* Write the message to the database */
             String msgBody = ((BluetoothMapbMessageEmail) msg).getEmailBody();
             if (V) {
                 int length = msgBody.length();
                 Log.v(TAG, "pushMessage: message string length = " + length);
-                String messages[] = msgBody.split("\r\n");
+                String[] messages = msgBody.split("\r\n");
                 Log.v(TAG, "pushMessage: messages count=" + messages.length);
-                for(int i = 0; i < messages.length; i++) {
+                for (int i = 0; i < messages.length; i++) {
                     Log.v(TAG, "part " + i + ":" + messages[i]);
                 }
             }
             FileOutputStream os = null;
             ParcelFileDescriptor fdOut = null;
             Uri uriInsert = Uri.parse(emailBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-            if (D) Log.d(TAG, "pushMessage - uriInsert= " + uriInsert.toString() +
-                    ", intoFolder id=" + folderElement.getFolderId());
+            if (D) {
+                Log.d(TAG, "pushMessage - uriInsert= " + uriInsert.toString() + ", intoFolder id="
+                        + folderElement.getFolderId());
+            }
 
-            synchronized(getMsgListMsg()) {
+            synchronized (getMsgListMsg()) {
                 // Now insert the empty message into folder
                 ContentValues values = new ContentValues();
                 folderId = folderElement.getFolderId();
                 values.put(BluetoothMapContract.MessageColumns.FOLDER_ID, folderId);
                 Uri uriNew = mProviderClient.insert(uriInsert, values);
-                if (D) Log.d(TAG, "pushMessage - uriNew= " + uriNew.toString());
-                handle =  Long.parseLong(uriNew.getLastPathSegment());
+                if (D) {
+                    Log.d(TAG, "pushMessage - uriNew= " + uriNew.toString());
+                }
+                handle = Long.parseLong(uriNew.getLastPathSegment());
 
                 try {
                     fdOut = mProviderClient.openFile(uriNew, "w");
@@ -2504,37 +2609,42 @@
                     os.write(msgBody.getBytes(), 0, msgBody.getBytes().length);
                 } catch (FileNotFoundException e) {
                     Log.w(TAG, e);
-                    throw(new IOException("Unable to open file stream"));
+                    throw (new IOException("Unable to open file stream"));
                 } catch (NullPointerException e) {
                     Log.w(TAG, e);
-                    throw(new IllegalArgumentException("Unable to parse message."));
+                    throw (new IllegalArgumentException("Unable to parse message."));
                 } finally {
                     try {
-                        if(os != null)
+                        if (os != null) {
                             os.close();
-                    } catch (IOException e) {Log.w(TAG, e);}
+                        }
+                    } catch (IOException e) {
+                        Log.w(TAG, e);
+                    }
                     try {
-                        if(fdOut != null)
+                        if (fdOut != null) {
                             fdOut.close();
-                    } catch (IOException e) {Log.w(TAG, e);}
+                        }
+                    } catch (IOException e) {
+                        Log.w(TAG, e);
+                    }
                 }
 
                 /* Extract the data for the inserted message, and store in local mirror, to
                  * avoid sending a NewMessage Event. */
                 /*TODO: We need to add the new 1.1 parameter as well:-) e.g. read*/
                 Msg newMsg = new Msg(handle, folderId, 1); // TODO: Create define for read-state
-                newMsg.transparent = (transparent == 1) ? true : false;
-                if ( folderId == folderElement.getFolderByName(
-                        BluetoothMapContract.FOLDER_NAME_OUTBOX).getFolderId() ) {
+                newMsg.transparent = transparent == 1;
+                if (folderId == folderElement.getFolderByName(
+                        BluetoothMapContract.FOLDER_NAME_OUTBOX).getFolderId()) {
                     newMsg.localInitiatedSend = true;
                 }
                 getMsgListMsg().put(handle, newMsg);
             }
         } else { // type SMS_* of MMS
-            for (BluetoothMapbMessage.vCard recipient : recipientList) {
+            for (BluetoothMapbMessage.VCard recipient : recipientList) {
                 // Only send the message to the top level recipient
-                if(recipient.getEnvLevel() == 0)
-                {
+                if (recipient.getEnvLevel() == 0) {
                     /* Only send to first address */
                     String phone = recipient.getFirstPhoneNumber();
                     String email = recipient.getFirstEmail();
@@ -2546,19 +2656,23 @@
                     /* If MMS contains text only and the size is less than ten SMS's
                      * then convert the MMS to type SMS and then proceed
                      */
-                    if (msg.getType().equals(TYPE.MMS) &&
-                            (((BluetoothMapbMessageMime) msg).getTextOnly() == true)) {
+                    if (msg.getType().equals(TYPE.MMS)
+                            && (((BluetoothMapbMessageMime) msg).getTextOnly())) {
                         msgBody = ((BluetoothMapbMessageMime) msg).getMessageAsText();
                         SmsManager smsMng = SmsManager.getDefault();
                         ArrayList<String> parts = smsMng.divideMessage(msgBody);
                         int smsParts = parts.size();
-                        if (smsParts  <= CONVERT_MMS_TO_SMS_PART_COUNT ) {
-                            if (D) Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts="
-                                    + smsParts );
+                        if (smsParts <= CONVERT_MMS_TO_SMS_PART_COUNT) {
+                            if (D) {
+                                Log.d(TAG, "pushMessage - converting MMS to SMS, sms parts="
+                                        + smsParts);
+                            }
                             msg.setType(mSmsType);
                         } else {
-                            if (D) Log.d(TAG, "pushMessage - MMS text only but to big to " +
-                                    "convert to SMS");
+                            if (D) {
+                                Log.d(TAG, "pushMessage - MMS text only but to big to "
+                                        + "convert to SMS");
+                            }
                             msgBody = null;
                         }
 
@@ -2566,13 +2680,14 @@
 
                     if (msg.getType().equals(TYPE.MMS)) {
                         /* Send message if folder is outbox else just store in draft*/
-                        handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMime)msg,
+                        handle = sendMmsMessage(folder, phone, (BluetoothMapbMessageMime) msg,
                                 transparent, retry);
-                    } else if (msg.getType().equals(TYPE.SMS_GSM) ||
-                            msg.getType().equals(TYPE.SMS_CDMA) ) {
+                    } else if (msg.getType().equals(TYPE.SMS_GSM) || msg.getType()
+                            .equals(TYPE.SMS_CDMA)) {
                         /* Add the message to the database */
-                        if(msgBody == null)
+                        if (msgBody == null) {
                             msgBody = ((BluetoothMapbMessageSms) msg).getSmsBody();
+                        }
 
                         if (TextUtils.isEmpty(msgBody)) {
                             Log.d(TAG, "PushMsg: Empty msgBody ");
@@ -2581,16 +2696,19 @@
                         }
                         /* We need to lock the SMS list while updating the database,
                          * to avoid sending events on MCE initiated operation. */
-                        Uri contentUri = Uri.parse(Sms.CONTENT_URI+ "/" + folder);
+                        Uri contentUri = Uri.parse(Sms.CONTENT_URI + "/" + folder);
                         Uri uri;
-                        synchronized(getMsgListSms()) {
-                            uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody,
-                                    "", System.currentTimeMillis(), read, deliveryReport);
+                        synchronized (getMsgListSms()) {
+                            uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody, "",
+                                    System.currentTimeMillis(), read, deliveryReport);
 
-                            if(V) Log.v(TAG, "Sms.addMessageToUri() returned: " + uri);
+                            if (V) {
+                                Log.v(TAG, "Sms.addMessageToUri() returned: " + uri);
+                            }
                             if (uri == null) {
-                                if (D) Log.d(TAG, "pushMessage - failure on add to uri "
-                                        + contentUri);
+                                if (D) {
+                                    Log.d(TAG, "pushMessage - failure on add to uri " + contentUri);
+                                }
                                 return -1;
                             }
                             Cursor c = mResolver.query(uri, SMS_PROJECTION_SHORT, null, null, null);
@@ -2603,38 +2721,46 @@
                                     int type = c.getInt(c.getColumnIndex(Sms.TYPE));
                                     int threadId = c.getInt(c.getColumnIndex(Sms.THREAD_ID));
                                     int readFlag = c.getInt(c.getColumnIndex(Sms.READ));
-                                    if(V) Log.v(TAG, "add message with id=" + id +
-                                            " type=" + type + " threadId=" + threadId +
-                                            " readFlag=" + readFlag + "to mMsgListSms");
+                                    if (V) {
+                                        Log.v(TAG, "add message with id=" + id + " type=" + type
+                                                + " threadId=" + threadId + " readFlag=" + readFlag
+                                                + "to mMsgListSms");
+                                    }
                                     Msg newMsg = new Msg(id, type, threadId, readFlag);
                                     getMsgListSms().put(id, newMsg);
                                     c.close();
                                 } else {
-                                    Log.w(TAG,"Message: " + uri + " no longer exist!");
+                                    Log.w(TAG, "Message: " + uri + " no longer exist!");
                                     /* This can only happen, if the message is deleted
                                      * just as it is added */
                                     return -1;
                                 }
                             } finally {
-                                if (c != null) c.close();
+                                if (c != null) {
+                                    c.close();
+                                }
                             }
 
                             handle = Long.parseLong(uri.getLastPathSegment());
 
                             /* Send message if folder is outbox */
                             if (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
-                                PushMsgInfo msgInfo = new PushMsgInfo(handle, transparent,
-                                        retry, phone, uri);
+                                PushMsgInfo msgInfo =
+                                        new PushMsgInfo(handle, transparent, retry, phone, uri);
                                 mPushMsgList.put(handle, msgInfo);
                                 sendMessage(msgInfo, msgBody);
-                                if(V) Log.v(TAG, "sendMessage returned...");
+                                if (V) {
+                                    Log.v(TAG, "sendMessage returned...");
+                                }
                             } /* else just added to draft */
 
                             /* sendMessage causes the message to be deleted and reinserted,
                              * hence we need to lock the list while this is happening. */
                         }
                     } else {
-                        if (D) Log.d(TAG, "pushMessage - failure on type " );
+                        if (D) {
+                            Log.d(TAG, "pushMessage - failure on type ");
+                        }
                         return -1;
                     }
                 }
@@ -2645,7 +2771,7 @@
         return handle;
     }
 
-    public long sendMmsMessage(String folder, String to_address, BluetoothMapbMessageMime msg,
+    public long sendMmsMessage(String folder, String toAddress, BluetoothMapbMessageMime msg,
             int transparent, int retry) {
         /*
          *strategy:
@@ -2659,14 +2785,15 @@
          *1) push message to folder
          * */
         if (folder != null && (folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)
-                ||  folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT))) {
-            long handle = pushMmsToFolder(Mms.MESSAGE_BOX_DRAFTS, to_address, msg);
+                || folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT))) {
+            long handle = pushMmsToFolder(Mms.MESSAGE_BOX_DRAFTS, toAddress, msg);
             /* if invalid handle (-1) then just return the handle
              * - else continue sending (if folder is outbox) */
-            if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle &&
-                    folder.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
+            if (BluetoothMapAppParams.INVALID_VALUE_PARAMETER != handle && folder.equalsIgnoreCase(
+                    BluetoothMapContract.FOLDER_NAME_OUTBOX)) {
                 Uri btMmsUri = MmsFileProvider.CONTENT_URI.buildUpon()
-                        .appendPath(Long.toString(handle)).build();
+                        .appendPath(Long.toString(handle))
+                        .build();
                 Intent sentIntent = new Intent(ACTION_MESSAGE_SENT);
                 // TODO: update the mmsMsgList <- done in pushMmsToFolder() but check
                 sentIntent.setType("message/" + Long.toString(handle));
@@ -2675,17 +2802,18 @@
                 sentIntent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, transparent);
                 sentIntent.putExtra(EXTRA_MESSAGE_SENT_RETRY, retry);
                 //sentIntent.setDataAndNormalize(btMmsUri);
-                PendingIntent pendingSendIntent = PendingIntent.getBroadcast(mContext, 0,
-                        sentIntent, 0);
-                SmsManager.getDefault().sendMultimediaMessage(mContext,
-                        btMmsUri, null/*locationUrl*/, null/*configOverrides*/,
-                        pendingSendIntent);
+                PendingIntent pendingSendIntent =
+                        PendingIntent.getBroadcast(mContext, 0, sentIntent, 0);
+                SmsManager.getDefault()
+                        .sendMultimediaMessage(mContext, btMmsUri, null/*locationUrl*/,
+                                null/*configOverrides*/,
+                                pendingSendIntent);
             }
             return handle;
         } else {
             /* not allowed to push mms to anything but outbox/draft */
-            throw  new IllegalArgumentException("Cannot push message to other " +
-                    "folders than outbox/draft");
+            throw new IllegalArgumentException(
+                    "Cannot push message to other " + "folders than outbox/draft");
         }
     }
 
@@ -2713,17 +2841,22 @@
                         /* set folder to be outbox */
                         data.put(Mms.MESSAGE_BOX, folder);
                         resolver.update(uri, data, whereClause, null);
-                        if (D) Log.d(TAG, "moved MMS message to " + getMmsFolderName(folder));
+                        if (D) {
+                            Log.d(TAG, "moved MMS message to " + getMmsFolderName(folder));
+                        }
                     }
                 } else {
                     Log.w(TAG, "Could not move MMS message to " + getMmsFolderName(folder));
                 }
             } finally {
-                if (queryResult != null) queryResult.close();
+                if (queryResult != null) {
+                    queryResult.close();
+                }
             }
         }
     }
-    private long pushMmsToFolder(int folder, String to_address, BluetoothMapbMessageMime msg) {
+
+    private long pushMmsToFolder(int folder, String toAddress, BluetoothMapbMessageMime msg) {
         /**
          * strategy:
          * 1) parse msg into parts + header
@@ -2736,13 +2869,13 @@
         values.put(Mms.MESSAGE_BOX, folder);
         values.put(Mms.READ, 0);
         values.put(Mms.SEEN, 0);
-        if(msg.getSubject() != null) {
+        if (msg.getSubject() != null) {
             values.put(Mms.SUBJECT, msg.getSubject());
         } else {
             values.put(Mms.SUBJECT, "");
         }
 
-        if(msg.getSubject() != null && msg.getSubject().length() > 0) {
+        if (msg.getSubject() != null && msg.getSubject().length() > 0) {
             values.put(Mms.SUBJECT_CHARSET, 106);
         }
         values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related");
@@ -2752,16 +2885,17 @@
         values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION);
         values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL);
         values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO);
-        values.put(Mms.TRANSACTION_ID, "T"+ Long.toHexString(System.currentTimeMillis()));
+        values.put(Mms.TRANSACTION_ID, "T" + Long.toHexString(System.currentTimeMillis()));
         values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO);
         values.put(Mms.LOCKED, 0);
-        if(msg.getTextOnly() == true)
+        if (msg.getTextOnly()) {
             values.put(Mms.TEXT_ONLY, true);
+        }
         values.put(Mms.MESSAGE_SIZE, msg.getSize());
 
         // Get thread id
         Set<String> recipients = new HashSet<String>();
-        recipients.addAll(Arrays.asList(to_address));
+        recipients.addAll(Arrays.asList(toAddress));
         values.put(Mms.THREAD_ID, Telephony.Threads.getOrCreateThreadId(mContext, recipients));
         Uri uri = Mms.CONTENT_URI;
 
@@ -2794,97 +2928,111 @@
                     c.close();
                 }
             } finally {
-                if (c != null) c.close();
+                if (c != null) {
+                    c.close();
+                }
             }
         } // Done adding changes, unlock access to mMsgListMms to allow sending MMS events again
 
         long handle = Long.parseLong(uri.getLastPathSegment());
-        if (V) Log.v(TAG, " NEW URI " + uri.toString());
+        if (V) {
+            Log.v(TAG, " NEW URI " + uri.toString());
+        }
 
         try {
-            if(msg.getMimeParts() == null) {
+            if (msg.getMimeParts() == null) {
                 /* Perhaps this message have been deleted, and no longer have any content,
                  * but only headers */
                 Log.w(TAG, "No MMS parts present...");
             } else {
-                if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size()
-                        + " parts to the data base.");
-                for(MimePart part : msg.getMimeParts()) {
+                if (V) {
+                    Log.v(TAG, "Adding " + msg.getMimeParts().size() + " parts to the data base.");
+                }
+                for (MimePart part : msg.getMimeParts()) {
                     int count = 0;
                     count++;
                     values.clear();
-                    if(part.mContentType != null &&
-                            part.mContentType.toUpperCase().contains("TEXT")) {
+                    if (part.mContentType != null && part.mContentType.toUpperCase()
+                            .contains("TEXT")) {
                         values.put(Mms.Part.CONTENT_TYPE, "text/plain");
                         values.put(Mms.Part.CHARSET, 106);
-                        if(part.mPartName != null) {
+                        if (part.mPartName != null) {
                             values.put(Mms.Part.FILENAME, part.mPartName);
                             values.put(Mms.Part.NAME, part.mPartName);
                         } else {
-                            values.put(Mms.Part.FILENAME, "text_" + count +".txt");
-                            values.put(Mms.Part.NAME, "text_" + count +".txt");
+                            values.put(Mms.Part.FILENAME, "text_" + count + ".txt");
+                            values.put(Mms.Part.NAME, "text_" + count + ".txt");
                         }
                         // Ensure we have "ci" set
-                        if(part.mContentId != null) {
+                        if (part.mContentId != null) {
                             values.put(Mms.Part.CONTENT_ID, part.mContentId);
                         } else {
-                            if(part.mPartName != null) {
+                            if (part.mPartName != null) {
                                 values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">");
                             } else {
                                 values.put(Mms.Part.CONTENT_ID, "<text_" + count + ">");
                             }
                         }
                         // Ensure we have "cl" set
-                        if(part.mContentLocation != null) {
+                        if (part.mContentLocation != null) {
                             values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation);
                         } else {
-                            if(part.mPartName != null) {
+                            if (part.mPartName != null) {
                                 values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".txt");
                             } else {
                                 values.put(Mms.Part.CONTENT_LOCATION, "text_" + count + ".txt");
                             }
                         }
 
-                        if(part.mContentDisposition != null) {
+                        if (part.mContentDisposition != null) {
                             values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition);
                         }
                         values.put(Mms.Part.TEXT, part.getDataAsString());
                         uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part");
                         uri = mResolver.insert(uri, values);
-                        if(V) Log.v(TAG, "Added TEXT part");
+                        if (V) {
+                            Log.v(TAG, "Added TEXT part");
+                        }
 
-                    } else if (part.mContentType != null &&
-                            part.mContentType.toUpperCase().contains("SMIL")){
+                    } else if (part.mContentType != null && part.mContentType.toUpperCase()
+                            .contains("SMIL")) {
                         values.put(Mms.Part.SEQ, -1);
                         values.put(Mms.Part.CONTENT_TYPE, "application/smil");
-                        if(part.mContentId != null) {
+                        if (part.mContentId != null) {
                             values.put(Mms.Part.CONTENT_ID, part.mContentId);
                         } else {
                             values.put(Mms.Part.CONTENT_ID, "<smil_" + count + ">");
                         }
-                        if(part.mContentLocation != null) {
+                        if (part.mContentLocation != null) {
                             values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation);
                         } else {
                             values.put(Mms.Part.CONTENT_LOCATION, "smil_" + count + ".xml");
                         }
 
-                        if(part.mContentDisposition != null)
+                        if (part.mContentDisposition != null) {
                             values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition);
+                        }
                         values.put(Mms.Part.FILENAME, "smil.xml");
                         values.put(Mms.Part.NAME, "smil.xml");
                         values.put(Mms.Part.TEXT, new String(part.mData, "UTF-8"));
 
-                        uri = Uri.parse(Mms.CONTENT_URI+ "/" + handle + "/part");
+                        uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/part");
                         uri = mResolver.insert(uri, values);
-                        if (V) Log.v(TAG, "Added SMIL part");
+                        if (V) {
+                            Log.v(TAG, "Added SMIL part");
+                        }
 
-                    }else /*VIDEO/AUDIO/IMAGE*/ {
+                    } else /*VIDEO/AUDIO/IMAGE*/ {
                         writeMmsDataPart(handle, part, count);
-                        if (V) Log.v(TAG, "Added OTHER part");
+                        if (V) {
+                            Log.v(TAG, "Added OTHER part");
+                        }
                     }
-                    if (uri != null){
-                        if (V) Log.v(TAG, "Added part with content-type: " + part.mContentType
-                                + " to Uri: " + uri.toString());
+                    if (uri != null) {
+                        if (V) {
+                            Log.v(TAG, "Added part with content-type: " + part.mContentType
+                                    + " to Uri: " + uri.toString());
+                        }
                     }
                 }
             }
@@ -2900,57 +3048,58 @@
         values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_FROM);
         values.put(Mms.Addr.CHARSET, 106);
 
-        uri = Uri.parse(Mms.CONTENT_URI + "/"  + handle + "/addr");
+        uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr");
         uri = mResolver.insert(uri, values);
-        if (uri != null && V){
+        if (uri != null && V) {
             Log.v(TAG, " NEW URI " + uri.toString());
         }
 
         values.clear();
         values.put(Mms.Addr.CONTACT_ID, "null");
-        values.put(Mms.Addr.ADDRESS, to_address);
+        values.put(Mms.Addr.ADDRESS, toAddress);
         values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_TO);
         values.put(Mms.Addr.CHARSET, 106);
 
-        uri = Uri.parse(Mms.CONTENT_URI + "/"  + handle + "/addr");
+        uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr");
         uri = mResolver.insert(uri, values);
-        if (uri != null && V){
+        if (uri != null && V) {
             Log.v(TAG, " NEW URI " + uri.toString());
         }
         return handle;
     }
 
 
-    private void writeMmsDataPart(long handle, MimePart part, int count) throws IOException{
+    private void writeMmsDataPart(long handle, MimePart part, int count) throws IOException {
         ContentValues values = new ContentValues();
         values.put(Mms.Part.MSG_ID, handle);
-        if(part.mContentType != null) {
+        if (part.mContentType != null) {
             values.put(Mms.Part.CONTENT_TYPE, part.mContentType);
         } else {
             Log.w(TAG, "MMS has no CONTENT_TYPE for part " + count);
         }
-        if(part.mContentId != null) {
+        if (part.mContentId != null) {
             values.put(Mms.Part.CONTENT_ID, part.mContentId);
         } else {
-            if(part.mPartName != null) {
+            if (part.mPartName != null) {
                 values.put(Mms.Part.CONTENT_ID, "<" + part.mPartName + ">");
             } else {
                 values.put(Mms.Part.CONTENT_ID, "<part_" + count + ">");
             }
         }
 
-        if(part.mContentLocation != null) {
+        if (part.mContentLocation != null) {
             values.put(Mms.Part.CONTENT_LOCATION, part.mContentLocation);
         } else {
-            if(part.mPartName != null) {
+            if (part.mPartName != null) {
                 values.put(Mms.Part.CONTENT_LOCATION, part.mPartName + ".dat");
             } else {
                 values.put(Mms.Part.CONTENT_LOCATION, "part_" + count + ".dat");
             }
         }
-        if(part.mContentDisposition != null)
+        if (part.mContentDisposition != null) {
             values.put(Mms.Part.CONTENT_DISPOSITION, part.mContentDisposition);
-        if(part.mPartName != null) {
+        }
+        if (part.mPartName != null) {
             values.put(Mms.Part.FILENAME, part.mPartName);
             values.put(Mms.Part.NAME, part.mPartName);
         } else {
@@ -2997,12 +3146,13 @@
                  * thereby get an intent for each msg part.
                  * setType is needed to create different intents for each message id/ time stamp,
                  * as the extras are not used when comparing. */
-                intentDelivery.setType("message/" + Long.toString(msgInfo.id) +
-                        msgInfo.timestamp + i);
+                intentDelivery.setType(
+                        "message/" + Long.toString(msgInfo.id) + msgInfo.timestamp + i);
                 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id);
                 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, msgInfo.timestamp);
-                PendingIntent pendingIntentDelivery = PendingIntent.getBroadcast(mContext, 0,
-                        intentDelivery, PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent pendingIntentDelivery =
+                        PendingIntent.getBroadcast(mContext, 0, intentDelivery,
+                                PendingIntent.FLAG_UPDATE_CURRENT);
 
                 intentSent = new Intent(ACTION_MESSAGE_SENT, null);
                 /* Add msgId and part number to ensure the intents are different, and we
@@ -3015,8 +3165,9 @@
                 intentSent.putExtra(EXTRA_MESSAGE_SENT_RETRY, msgInfo.retry);
                 intentSent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, msgInfo.transparent);
 
-                PendingIntent pendingIntentSent = PendingIntent.getBroadcast(mContext, 0,
-                        intentSent, PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent pendingIntentSent =
+                        PendingIntent.getBroadcast(mContext, 0, intentSent,
+                                PendingIntent.FLAG_UPDATE_CURRENT);
 
                 // We use the same pending intent for all parts, but do not set the one shot flag.
                 deliveryIntents.add(pendingIntentDelivery);
@@ -3025,21 +3176,26 @@
 
             Log.d(TAG, "sendMessage to " + msgInfo.phone);
 
-            smsMng.sendMultipartTextMessage(msgInfo.phone, null, parts, sentIntents,
-                    deliveryIntents);
+            if (parts.size() == 1) {
+                smsMng.sendTextMessageWithoutPersisting(msgInfo.phone, null, parts.get(0),
+                        sentIntents.get(0), deliveryIntents.get(0));
+            } else {
+                smsMng.sendMultipartTextMessageWithoutPersisting(msgInfo.phone, null, parts,
+                        sentIntents, deliveryIntents);
+            }
         }
     }
 
-    private class SmsBroadcastReceiver extends BroadcastReceiver {
-        private final String[] ID_PROJECTION = new String[] { Sms._ID };
-        private final Uri UPDATE_STATUS_URI = Uri.withAppendedPath(Sms.CONTENT_URI, "/status");
+    private static final String[] ID_PROJECTION = new String[]{Sms._ID};
+    private static final Uri UPDATE_STATUS_URI = Uri.withAppendedPath(Sms.CONTENT_URI, "/status");
 
+    private class SmsBroadcastReceiver extends BroadcastReceiver {
         public void register() {
             Handler handler = new Handler(Looper.getMainLooper());
 
             IntentFilter intentFilter = new IntentFilter();
             intentFilter.addAction(ACTION_MESSAGE_DELIVERY);
-            try{
+            try {
                 intentFilter.addDataType("message/*");
             } catch (MalformedMimeTypeException e) {
                 Log.e(TAG, "Wrong mime type!!!", e);
@@ -3062,7 +3218,7 @@
             long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
             PushMsgInfo msgInfo = mPushMsgList.get(handle);
 
-            Log.d(TAG, "onReceive: action"  + action);
+            Log.d(TAG, "onReceive: action" + action);
 
             if (msgInfo == null) {
                 Log.d(TAG, "onReceive: no msgInfo found for handle " + handle);
@@ -3070,66 +3226,53 @@
             }
 
             if (action.equals(ACTION_MESSAGE_SENT)) {
-                int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT,
-                        Activity.RESULT_CANCELED);
+                int result =
+                        intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
                 msgInfo.partsSent++;
-                if(result != Activity.RESULT_OK) {
+                if (result != Activity.RESULT_OK) {
                     /* If just one of the parts in the message fails, we need to send the
                      * entire message again
                      */
                     msgInfo.failedSent = true;
                 }
-                if(D) Log.d(TAG, "onReceive: msgInfo.partsSent = " + msgInfo.partsSent
-                        + ", msgInfo.parts = " + msgInfo.parts + " result = " + result);
+                if (D) {
+                    Log.d(TAG, "onReceive: msgInfo.partsSent = " + msgInfo.partsSent
+                            + ", msgInfo.parts = " + msgInfo.parts + " result = " + result);
+                }
 
                 if (msgInfo.partsSent == msgInfo.parts) {
-                    actionMessageSent(context, intent, msgInfo);
+                    actionMessageSent(context, intent, msgInfo, handle);
                 }
             } else if (action.equals(ACTION_MESSAGE_DELIVERY)) {
                 long timestamp = intent.getLongExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, 0);
                 int status = -1;
-                if(msgInfo.timestamp == timestamp) {
+                if (msgInfo.timestamp == timestamp) {
                     msgInfo.partsDelivered++;
-                    byte[] pdu = intent.getByteArrayExtra("pdu");
-                    String format = intent.getStringExtra("format");
-
-                    SmsMessage message = SmsMessage.createFromPdu(pdu, format);
-                    if (message == null) {
-                        Log.d(TAG, "actionMessageDelivery: Can't get message from pdu");
-                        return;
-                    }
-                    status = message.getStatus();
-                    if(status != 0/*0 is success*/) {
-                        msgInfo.statusDelivered = status;
-                        if(D) Log.d(TAG, "msgInfo.statusDelivered = " + status);
-                        Sms.moveMessageToFolder(mContext, msgInfo.uri, Sms.MESSAGE_TYPE_FAILED, 0);
-                    } else {
-                        Sms.moveMessageToFolder(mContext, msgInfo.uri, Sms.MESSAGE_TYPE_SENT, 0);
-                    }
-                }
-                if (msgInfo.partsDelivered == msgInfo.parts) {
-                    actionMessageDelivery(context, intent, msgInfo);
                 }
             } else {
                 Log.d(TAG, "onReceive: Unknown action " + action);
             }
         }
 
-        private void actionMessageSent(Context context, Intent intent, PushMsgInfo msgInfo) {
+        private void actionMessageSent(
+                Context context, Intent intent, PushMsgInfo msgInfo, long handle) {
             /* As the MESSAGE_SENT intent is forwarded from the MAP service, we use the intent
              * to carry the result, as getResult() will not return the correct value.
              */
             boolean delete = false;
 
-            if(D) Log.d(TAG,"actionMessageSent(): msgInfo.failedSent = " + msgInfo.failedSent);
+            if (D) {
+                Log.d(TAG, "actionMessageSent(): msgInfo.failedSent = " + msgInfo.failedSent);
+            }
 
             msgInfo.sendInProgress = false;
 
-            if (msgInfo.failedSent == false) {
-                if(D) Log.d(TAG, "actionMessageSent: result OK");
+            if (!msgInfo.failedSent) {
+                if (D) {
+                    Log.d(TAG, "actionMessageSent: result OK");
+                }
                 if (msgInfo.transparent == 0) {
-                    if (!Sms.moveMessageToFolder(context, msgInfo.uri,
-                            Sms.MESSAGE_TYPE_SENT, 0)) {
+                    if (!Sms.moveMessageToFolder(context, msgInfo.uri, Sms.MESSAGE_TYPE_SENT, 0)) {
                         Log.w(TAG, "Failed to move " + msgInfo.uri + " to SENT");
                     }
                 } else {
@@ -3151,8 +3294,8 @@
                     sendEvent(evt);
                 } else {
                     if (msgInfo.transparent == 0) {
-                        if (!Sms.moveMessageToFolder(context, msgInfo.uri,
-                                Sms.MESSAGE_TYPE_FAILED, 0)) {
+                        if (!Sms.moveMessageToFolder(context, msgInfo.uri, Sms.MESSAGE_TYPE_FAILED,
+                                0)) {
                             Log.w(TAG, "Failed to move " + msgInfo.uri + " to FAILED");
                         }
                     } else {
@@ -3165,14 +3308,16 @@
                 }
             }
 
-            if (delete == true) {
+            if (delete) {
                 /* Delete from Observer message list to avoid delete notifications */
-                synchronized(getMsgListSms()) {
+                synchronized (getMsgListSms()) {
                     getMsgListSms().remove(msgInfo.id);
                 }
 
                 /* Delete from DB */
-                mResolver.delete(msgInfo.uri, null, null);
+                Uri msgUri = ContentUris.withAppendedId(Sms.CONTENT_URI, handle);
+                int nRows = mResolver.delete(msgUri, null, null);
+                if (V && nRows > 0) Log.v(TAG, "Deleted message with Uri = " + msgUri);
             }
         }
 
@@ -3188,8 +3333,10 @@
 
                     Uri updateUri = ContentUris.withAppendedId(UPDATE_STATUS_URI, messageId);
 
-                    if(D) Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status="
-                            + msgInfo.statusDelivered);
+                    if (D) {
+                        Log.d(TAG, "actionMessageDelivery: uri=" + messageUri + ", status="
+                                + msgInfo.statusDelivered);
+                    }
 
                     ContentValues contentValues = new ContentValues(2);
 
@@ -3200,7 +3347,9 @@
                     Log.d(TAG, "Can't find message for status update: " + messageUri);
                 }
             } finally {
-                if (cursor != null) cursor.close();
+                if (cursor != null) {
+                    cursor.close();
+                }
             }
 
             if (msgInfo.statusDelivered == 0) {
@@ -3239,9 +3388,10 @@
             }
         }
 
+        @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            Log.d(TAG, "onReceive: action"  + action);
+            Log.d(TAG, "onReceive: action" + action);
 
             if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
                 try {
@@ -3270,7 +3420,7 @@
      * @param intent The intent received
      * @param result The result
      */
-    static public void actionMmsSent(Context context, Intent intent, int result,
+    public static void actionMmsSent(Context context, Intent intent, int result,
             Map<Long, Msg> mmsMsgList) {
         /*
          * if transparent:
@@ -3281,15 +3431,17 @@
          *   Result == Fail:
          *     move to outbox (send delivery fail notification)
          */
-        if(D) Log.d(TAG,"actionMmsSent()");
+        if (D) {
+            Log.d(TAG, "actionMmsSent()");
+        }
         int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
         long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
-        if(handle < 0) {
+        if (handle < 0) {
             Log.w(TAG, "Intent received for an invalid handle");
             return;
         }
         ContentResolver resolver = context.getContentResolver();
-        if(transparent == 1) {
+        if (transparent == 1) {
             /* The specification is a bit unclear about the transparent flag. If it is set
              * no copy of the message shall be kept in the send folder after the message
              * was send, but in the case of a send error, it is unclear what to do.
@@ -3298,23 +3450,25 @@
              * If we however do have a MNS connection we need to send a notification. */
             Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, handle);
             /* Delete from observer message list to avoid delete notifications */
-            if(mmsMsgList != null) {
-                synchronized(mmsMsgList) {
+            if (mmsMsgList != null) {
+                synchronized (mmsMsgList) {
                     mmsMsgList.remove(handle);
                 }
             }
             /* Delete message */
-            if(D) Log.d(TAG,"Transparent in use - delete");
+            if (D) {
+                Log.d(TAG, "Transparent in use - delete");
+            }
             resolver.delete(uri, null, null);
         } else if (result == Activity.RESULT_OK) {
             /* This will trigger a notification */
             moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_SENT);
         } else {
-            if(mmsMsgList != null) {
-                synchronized(mmsMsgList) {
+            if (mmsMsgList != null) {
+                synchronized (mmsMsgList) {
                     Msg msg = mmsMsgList.get(handle);
-                    if(msg != null) {
-                    msg.type=Mms.MESSAGE_BOX_OUTBOX;
+                    if (msg != null) {
+                        msg.type = Mms.MESSAGE_BOX_OUTBOX;
                     }
                 }
             }
@@ -3323,21 +3477,21 @@
         }
     }
 
-    static public void actionMessageSentDisconnected(Context context, Intent intent, int result) {
+    public static void actionMessageSentDisconnected(Context context, Intent intent, int result) {
         TYPE type = TYPE.fromOrdinal(
-        intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal()));
-        if(type == TYPE.MMS) {
+                intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal()));
+        if (type == TYPE.MMS) {
             actionMmsSent(context, intent, result, null);
         } else {
             actionSmsSentDisconnected(context, intent, result);
         }
     }
 
-    static public void actionSmsSentDisconnected(Context context, Intent intent, int result) {
+    public static void actionSmsSentDisconnected(Context context, Intent intent, int result) {
         /* Check permission for message deletion. */
-        if ((Binder.getCallingPid() != Process.myPid()) ||
-            (context.checkCallingOrSelfPermission("android.Manifest.permission.WRITE_SMS")
-                    != PackageManager.PERMISSION_GRANTED)) {
+        if ((Binder.getCallingPid() != Process.myPid()) || (
+                context.checkCallingOrSelfPermission("android.Manifest.permission.WRITE_SMS")
+                        != PackageManager.PERMISSION_GRANTED)) {
             Log.w(TAG, "actionSmsSentDisconnected: Not allowed to delete SMS/MMS messages");
             return;
         }
@@ -3346,7 +3500,7 @@
         //int retry = intent.getIntExtra(EXTRA_MESSAGE_SENT_RETRY, 0);
         int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
         String uriString = intent.getStringExtra(EXTRA_MESSAGE_SENT_URI);
-        if(uriString == null) {
+        if (uriString == null) {
             // Nothing we can do about it, just bail out
             return;
         }
@@ -3355,8 +3509,7 @@
         if (result == Activity.RESULT_OK) {
             Log.d(TAG, "actionMessageSentDisconnected: result OK");
             if (transparent == 0) {
-                if (!Sms.moveMessageToFolder(context, uri,
-                        Sms.MESSAGE_TYPE_SENT, 0)) {
+                if (!Sms.moveMessageToFolder(context, uri, Sms.MESSAGE_TYPE_SENT, 0)) {
                     Log.d(TAG, "Failed to move " + uri + " to SENT");
                 }
             } else {
@@ -3366,10 +3519,10 @@
             /*if (retry == 1) {
                  The retry feature only works while connected, else we fail the send,
              * and move the message to failed, to let the user/app resend manually later.
-            } else */{
+            } else */
+            {
                 if (transparent == 0) {
-                    if (!Sms.moveMessageToFolder(context, uri,
-                            Sms.MESSAGE_TYPE_FAILED, 0)) {
+                    if (!Sms.moveMessageToFolder(context, uri, Sms.MESSAGE_TYPE_FAILED, 0)) {
                         Log.d(TAG, "Failed to move " + uri + " to FAILED");
                     }
                 } else {
@@ -3390,14 +3543,14 @@
     }
 
     private void registerPhoneServiceStateListener() {
-        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
+        TelephonyManager tm =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         tm.listen(mPhoneListener, PhoneStateListener.LISTEN_SERVICE_STATE);
     }
 
     private void unRegisterPhoneServiceStateListener() {
-        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
+        TelephonyManager tm =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         tm.listen(mPhoneListener, PhoneStateListener.LISTEN_NONE);
     }
 
@@ -3405,17 +3558,17 @@
         /* Send pending messages in outbox */
         String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
         UserManager manager = UserManager.get(mContext);
-        if (manager == null || !manager.isUserUnlocked()) return;
-        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
-                null);
+        if (manager == null || !manager.isUserUnlocked()) {
+            return;
+        }
+        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, null);
         try {
             if (c != null && c.moveToFirst()) {
                 do {
                     long id = c.getLong(c.getColumnIndex(Sms._ID));
                     String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
                     PushMsgInfo msgInfo = mPushMsgList.get(id);
-                    if (msgInfo == null || msgInfo.resend == false ||
-                            msgInfo.sendInProgress == true) {
+                    if (msgInfo == null || !msgInfo.resend || msgInfo.sendInProgress) {
                         continue;
                     }
                     msgInfo.sendInProgress = true;
@@ -3423,7 +3576,9 @@
                 } while (c.moveToNext());
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
 
 
@@ -3432,31 +3587,30 @@
     private void failPendingMessages() {
         /* Move pending messages from outbox to failed */
         String where = "type = " + Sms.MESSAGE_TYPE_OUTBOX;
-        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
-                null);
+        Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, null);
         try {
             if (c != null && c.moveToFirst()) {
                 do {
                     long id = c.getLong(c.getColumnIndex(Sms._ID));
                     String msgBody = c.getString(c.getColumnIndex(Sms.BODY));
                     PushMsgInfo msgInfo = mPushMsgList.get(id);
-                    if (msgInfo == null || msgInfo.resend == false) {
+                    if (msgInfo == null || !msgInfo.resend) {
                         continue;
                     }
-                    Sms.moveMessageToFolder(mContext, msgInfo.uri,
-                            Sms.MESSAGE_TYPE_FAILED, 0);
+                    Sms.moveMessageToFolder(mContext, msgInfo.uri, Sms.MESSAGE_TYPE_FAILED, 0);
                 } while (c.moveToNext());
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
 
     }
 
     private void removeDeletedMessages() {
         /* Remove messages from virtual "deleted" folder (thread_id -1) */
-        mResolver.delete(Sms.CONTENT_URI,
-                "thread_id = " + DELETED_THREAD_ID, null);
+        mResolver.delete(Sms.CONTENT_URI, "thread_id = " + DELETED_THREAD_ID, null);
     }
 
     private PhoneStateListener mPhoneListener = new PhoneStateListener() {
@@ -3489,17 +3643,19 @@
             mSmsBroadcastReceiver.unregister();
         }
         unRegisterPhoneServiceStateListener();
-        failPendingMessages();
-        removeDeletedMessages();
+        if (UserManager.get(mContext).isUserUnlocked()) {
+            failPendingMessages();
+            removeDeletedMessages();
+        }
     }
 
-    public boolean handleSmsSendIntent(Context context, Intent intent){
+    public boolean handleSmsSendIntent(Context context, Intent intent) {
         TYPE type = TYPE.fromOrdinal(
-            intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal()));
-        if(type == TYPE.MMS) {
+                intent.getIntExtra(EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal()));
+        if (type == TYPE.MMS) {
             return handleMmsSendIntent(context, intent);
         } else {
-            if(mInitialized) {
+            if (mInitialized) {
                 mSmsBroadcastReceiver.onReceive(context, intent);
                 return true;
             }
@@ -3507,30 +3663,34 @@
         return false;
     }
 
-    public boolean handleMmsSendIntent(Context context, Intent intent){
-        if(D) Log.w(TAG, "handleMmsSendIntent()");
-        if(mMnsClient.isConnected() == false) {
+    public boolean handleMmsSendIntent(Context context, Intent intent) {
+        if (D) {
+            Log.w(TAG, "handleMmsSendIntent()");
+        }
+        if (!mMnsClient.isConnected()) {
             // No need to handle notifications, just use default handling
-            if(D) Log.w(TAG, "MNS not connected - use static handling");
+            if (D) {
+                Log.w(TAG, "MNS not connected - use static handling");
+            }
             return false;
         }
         long handle = intent.getLongExtra(EXTRA_MESSAGE_SENT_HANDLE, -1);
         int result = intent.getIntExtra(EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
         actionMmsSent(context, intent, result, getMsgListMms());
-        if(handle < 0) {
+        if (handle < 0) {
             Log.w(TAG, "Intent received for an invalid handle");
             return true;
         }
-        if(result != Activity.RESULT_OK) {
-            if(mObserverRegistered) {
+        if (result != Activity.RESULT_OK) {
+            if (mObserverRegistered) {
                 Event evt = new Event(EVENT_TYPE_SENDING_FAILURE, handle,
                         getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS);
                 sendEvent(evt);
             }
         } else {
             int transparent = intent.getIntExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
-            if(transparent != 0) {
-                if(mObserverRegistered) {
+            if (transparent != 0) {
+                if (mObserverRegistered) {
                     Event evt = new Event(EVENT_TYPE_SENDING_SUCCESS, handle,
                             getMmsFolderName(Mms.MESSAGE_BOX_OUTBOX), null, TYPE.MMS);
                     sendEvent(evt);
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java b/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java
index d141650..f389865 100644
--- a/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoContactElement.java
@@ -14,26 +14,26 @@
 */
 package com.android.bluetooth.map;
 
+import android.util.Log;
+
+import com.android.bluetooth.SignedLongLong;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.util.Log;
-
-import com.android.bluetooth.SignedLongLong;
-
 public class BluetoothMapConvoContactElement
-    implements Comparable<BluetoothMapConvoContactElement> {
+        implements Comparable<BluetoothMapConvoContactElement> {
 
     public static final long CONTACT_ID_TYPE_SMS_MMS = 1;
-    public static final long CONTACT_ID_TYPE_EMAIL   = 2;
-    public static final long CONTACT_ID_TYPE_IM      = 3;
+    public static final long CONTACT_ID_TYPE_EMAIL = 2;
+    public static final long CONTACT_ID_TYPE_IM = 3;
 
     private static final String XML_ATT_PRIORITY = "priority";
     private static final String XML_ATT_PRESENCE_STATUS = "presence_status";
@@ -64,7 +64,7 @@
         BluetoothMapConvoContactElement newElement = new BluetoothMapConvoContactElement();
         newElement.mUci = address;
         // TODO: For now we use the ID as BT-UID
-        newElement.mBtUid = new SignedLongLong(contact.getId(),0);
+        newElement.mBtUid = new SignedLongLong(contact.getId(), 0);
         newElement.mDisplayName = contact.getName();
         return newElement;
     }
@@ -81,11 +81,11 @@
         this.mChatState = chatState;
         this.mPresenceStatus = presenceStatus;
         this.mPriority = priority;
-        if(btUid != null) {
+        if (btUid != null) {
             try {
                 this.mBtUid = SignedLongLong.fromString(btUid);
             } catch (UnsupportedEncodingException e) {
-                Log.w(TAG,e);
+                Log.w(TAG, e);
             }
         }
     }
@@ -175,10 +175,11 @@
         this.mUci = uci;
     }
 
-    public String getContactId(){
+    public String getContactId() {
         return mUci;
     }
 
+    @Override
     public int compareTo(BluetoothMapConvoContactElement e) {
         if (this.mLastActivity < e.mLastActivity) {
             return 1;
@@ -193,43 +194,41 @@
      * Here we have taken the choice not to report empty attributes, to reduce the
      * amount of data to be transfered over BT. */
     public void encode(XmlSerializer xmlConvoElement)
-            throws IllegalArgumentException, IllegalStateException, IOException
-    {
-            // construct the XML tag for a single contact in the convolisting element.
-            xmlConvoElement.startTag(null, XML_TAG_CONVOCONTACT);
-            if(mUci != null) {
-                xmlConvoElement.attribute(null, XML_ATT_UCI, mUci);
-            }
-            if(mDisplayName != null) {
-                xmlConvoElement.attribute(null, XML_ATT_DISPLAY_NAME,
-                            BluetoothMapUtils.stripInvalidChars(mDisplayName));
-            }
-            if(mName != null) {
-                xmlConvoElement.attribute(null, XML_ATT_NAME,
-                        BluetoothMapUtils.stripInvalidChars(mName));
-            }
-            if(mChatState != -1) {
-                xmlConvoElement.attribute(null, XML_ATT_CHAT_STATE, String.valueOf(mChatState));
-            }
-            if(mLastActivity != -1) {
-                xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY,
-                        this.getLastActivityString());
-            }
-            if(mBtUid != null) {
-                xmlConvoElement.attribute(null, XML_ATT_X_BT_UID, mBtUid.toHexString());
-            }
-            if(mPresenceAvailability != -1) {
-                xmlConvoElement.attribute(null,  XML_ATT_PRESENCE_AVAILABILITY,
-                        String.valueOf(mPresenceAvailability));
-            }
-            if(mPresenceStatus != null) {
-                xmlConvoElement.attribute(null,  XML_ATT_PRESENCE_STATUS, mPresenceStatus);
-            }
-            if(mPriority != -1) {
-                xmlConvoElement.attribute(null, XML_ATT_PRIORITY, String.valueOf(mPriority));
-            }
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        // construct the XML tag for a single contact in the convolisting element.
+        xmlConvoElement.startTag(null, XML_TAG_CONVOCONTACT);
+        if (mUci != null) {
+            xmlConvoElement.attribute(null, XML_ATT_UCI, mUci);
+        }
+        if (mDisplayName != null) {
+            xmlConvoElement.attribute(null, XML_ATT_DISPLAY_NAME,
+                    BluetoothMapUtils.stripInvalidChars(mDisplayName));
+        }
+        if (mName != null) {
+            xmlConvoElement.attribute(null, XML_ATT_NAME,
+                    BluetoothMapUtils.stripInvalidChars(mName));
+        }
+        if (mChatState != -1) {
+            xmlConvoElement.attribute(null, XML_ATT_CHAT_STATE, String.valueOf(mChatState));
+        }
+        if (mLastActivity != -1) {
+            xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY, this.getLastActivityString());
+        }
+        if (mBtUid != null) {
+            xmlConvoElement.attribute(null, XML_ATT_X_BT_UID, mBtUid.toHexString());
+        }
+        if (mPresenceAvailability != -1) {
+            xmlConvoElement.attribute(null, XML_ATT_PRESENCE_AVAILABILITY,
+                    String.valueOf(mPresenceAvailability));
+        }
+        if (mPresenceStatus != null) {
+            xmlConvoElement.attribute(null, XML_ATT_PRESENCE_STATUS, mPresenceStatus);
+        }
+        if (mPriority != -1) {
+            xmlConvoElement.attribute(null, XML_ATT_PRIORITY, String.valueOf(mPriority));
+        }
 
-            xmlConvoElement.endTag(null, XML_TAG_CONVOCONTACT);
+        xmlConvoElement.endTag(null, XML_TAG_CONVOCONTACT);
     }
 
 
@@ -244,34 +243,36 @@
             throws ParseException, XmlPullParserException, IOException {
         int count = parser.getAttributeCount();
         BluetoothMapConvoContactElement newElement;
-        if(count<1) {
-            throw new IllegalArgumentException(XML_TAG_CONVOCONTACT +
-                    " is not decorated with attributes");
+        if (count < 1) {
+            throw new IllegalArgumentException(
+                    XML_TAG_CONVOCONTACT + " is not decorated with attributes");
         }
         newElement = new BluetoothMapConvoContactElement();
-        for (int i = 0; i<count; i++) {
+        for (int i = 0; i < count; i++) {
             String attributeName = parser.getAttributeName(i).trim();
             String attributeValue = parser.getAttributeValue(i);
-            if(attributeName.equalsIgnoreCase(XML_ATT_UCI)) {
+            if (attributeName.equalsIgnoreCase(XML_ATT_UCI)) {
                 newElement.mUci = attributeValue;
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_NAME)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_NAME)) {
                 newElement.mName = attributeValue;
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_DISPLAY_NAME)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_DISPLAY_NAME)) {
                 newElement.mDisplayName = attributeValue;
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_CHAT_STATE)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_CHAT_STATE)) {
                 newElement.setChatState(attributeValue);
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) {
                 newElement.setLastActivity(attributeValue);
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_X_BT_UID)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_X_BT_UID)) {
                 newElement.setBtUid(SignedLongLong.fromString(attributeValue));
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_AVAILABILITY)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_AVAILABILITY)) {
                 newElement.mPresenceAvailability = Integer.parseInt(attributeValue);
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_STATUS)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_STATUS)) {
                 newElement.setPresenceStatus(attributeValue);
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_PRIORITY)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_PRIORITY)) {
                 newElement.setPriority(Integer.parseInt(attributeValue));
             } else {
-                if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
+                if (D) {
+                    Log.i(TAG, "Unknown XML attribute: " + parser.getAttributeName(i));
+                }
             }
         }
         parser.nextTag(); // Consume the end-tag
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListing.java b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
index cda95a6..f33ea64 100644
--- a/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
@@ -14,6 +14,16 @@
 */
 package com.android.bluetooth.map;
 
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringWriter;
@@ -23,33 +33,23 @@
 import java.util.Collections;
 import java.util.List;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.util.Log;
-import android.util.Xml;
-
-import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
-
 public class BluetoothMapConvoListing {
-    private boolean hasUnread = false;
+    private boolean mHasUnread = false;
     private static final String TAG = "BluetoothMapConvoListing";
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final String XML_TAG = "MAP-convo-listing";
 
     private List<BluetoothMapConvoListingElement> mList;
 
-    public BluetoothMapConvoListing(){
-     mList = new ArrayList<BluetoothMapConvoListingElement>();
+    public BluetoothMapConvoListing() {
+        mList = new ArrayList<BluetoothMapConvoListingElement>();
     }
+
     public void add(BluetoothMapConvoListingElement element) {
         mList.add(element);
         /* update info regarding whether the list contains unread conversations */
-        if (element.getReadBool())
-        {
-            hasUnread = true;
+        if (element.getReadBool()) {
+            mHasUnread = true;
         }
     }
 
@@ -58,8 +58,7 @@
      * @return the number of elements in the list.
      */
     public int getCount() {
-        if(mList != null)
-        {
+        if (mList != null) {
             return mList.size();
         }
         return 0;
@@ -69,9 +68,8 @@
      * does the list contain any unread messages
      * @return true if unread messages have been added to the list, else false
      */
-    public boolean hasUnread()
-    {
-        return hasUnread;
+    public boolean hasUnread() {
+        return mHasUnread;
     }
 
 
@@ -79,7 +77,7 @@
      *  returns the entire list as a list
      * @return list
      */
-    public List<BluetoothMapConvoListingElement> getList(){
+    public List<BluetoothMapConvoListingElement> getList() {
         return mList;
     }
 
@@ -125,15 +123,15 @@
         count = Math.min(count, mList.size() - offset);
         if (count > 0) {
             mList = mList.subList(offset, offset + count);
-            if(mList == null) {
+            if (mList == null) {
                 mList = new ArrayList<BluetoothMapConvoListingElement>(); // Return an empty list
             }
         } else {
-            if(offset > mList.size()) {
-               mList = new ArrayList<BluetoothMapConvoListingElement>();
-               Log.d(TAG, "offset greater than list size. Returning empty list");
+            if (offset > mList.size()) {
+                mList = new ArrayList<BluetoothMapConvoListingElement>();
+                Log.d(TAG, "offset greater than list size. Returning empty list");
             } else {
-               mList = mList.subList(offset, mList.size());
+                mList = mList.subList(offset, mList.size());
             }
         }
     }
@@ -146,16 +144,18 @@
             parser.setInput(xmlDocument, "UTF-8");
 
             // First find the folder-listing
-            while((type=parser.next()) != XmlPullParser.END_TAG
-                    && type != XmlPullParser.END_DOCUMENT ) {
+            while ((type = parser.next()) != XmlPullParser.END_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
                 // Skip until we get a start tag
                 if (parser.getEventType() != XmlPullParser.START_TAG) {
                     continue;
                 }
                 // Skip until we get a folder-listing tag
                 String name = parser.getName();
-                if(!name.equalsIgnoreCase(XML_TAG)) {
-                    if(D) Log.i(TAG,"Unknown XML tag: " + name);
+                if (!name.equalsIgnoreCase(XML_TAG)) {
+                    if (D) {
+                        Log.i(TAG, "Unknown XML tag: " + name);
+                    }
                     XmlUtils.skipCurrentTag(parser);
                 }
                 readConversations(parser);
@@ -175,18 +175,22 @@
     private void readConversations(XmlPullParser parser)
             throws XmlPullParserException, IOException, ParseException {
         int type;
-        if(D) Log.i(TAG,"readConversations(): ");
-        while((type=parser.next()) != XmlPullParser.END_TAG
-                && type != XmlPullParser.END_DOCUMENT ) {
+        if (D) {
+            Log.i(TAG, "readConversations(): ");
+        }
+        while ((type = parser.next()) != XmlPullParser.END_TAG
+                && type != XmlPullParser.END_DOCUMENT) {
             // Skip until we get a start tag
             if (parser.getEventType() != XmlPullParser.START_TAG) {
                 continue;
             }
             // Skip until we get a folder-listing tag
             String name = parser.getName();
-            if(name.trim().equalsIgnoreCase(BluetoothMapConvoListingElement.XML_TAG_CONVERSATION)
-                    == false) {
-                if(D) Log.i(TAG,"Unknown XML tag: " + name);
+            if (!name.trim()
+                    .equalsIgnoreCase(BluetoothMapConvoListingElement.XML_TAG_CONVERSATION)) {
+                if (D) {
+                    Log.i(TAG, "Unknown XML tag: " + name);
+                }
                 XmlUtils.skipCurrentTag(parser);
                 continue;
             }
@@ -208,7 +212,7 @@
             return false;
         }
         BluetoothMapConvoListing other = (BluetoothMapConvoListing) obj;
-        if (hasUnread != other.hasUnread) {
+        if (mHasUnread != other.mHasUnread) {
             return false;
         }
         if (mList == null) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
index 4947aa4..be28be0 100644
--- a/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
@@ -14,6 +14,16 @@
 */
 package com.android.bluetooth.map;
 
+import android.util.Log;
+
+import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.text.ParseException;
@@ -22,18 +32,8 @@
 import java.util.Date;
 import java.util.List;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.util.Log;
-
-import com.android.bluetooth.SignedLongLong;
-import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
-import com.android.internal.util.XmlUtils;
-
 public class BluetoothMapConvoListingElement
-    implements Comparable<BluetoothMapConvoListingElement> {
+        implements Comparable<BluetoothMapConvoListingElement> {
 
     public static final String XML_TAG_CONVERSATION = "conversation";
     private static final String XML_ATT_LAST_ACTIVITY = "last_activity";
@@ -57,7 +57,7 @@
     private TYPE mType = null;
     private String mSummary = null;
 
- // Used only to keep track of changes to convoListVersionCounter;
+    // Used only to keep track of changes to convoListVersionCounter;
     private String mSmsMmsContacts = null;
 
     public int getCursorIndex() {
@@ -66,15 +66,19 @@
 
     public void setCursorIndex(int cursorIndex) {
         this.mCursorIndex = cursorIndex;
-        if(D) Log.d(TAG, "setCursorIndex: " + cursorIndex);
+        if (D) {
+            Log.d(TAG, "setCursorIndex: " + cursorIndex);
+        }
     }
 
-    public long getVersionCounter(){
+    public long getVersionCounter() {
         return mVersionCounter;
     }
 
-    public void setVersionCounter(long vcount){
-        if(D) Log.d(TAG, "setVersionCounter: " + vcount);
+    public void setVersionCounter(long vcount) {
+        if (D) {
+            Log.d(TAG, "setVersionCounter: " + vcount);
+        }
         this.mVersionCounter = vcount;
     }
 
@@ -82,8 +86,10 @@
         mVersionCounter++;
     }
 
-    private void setVersionCounter(String vcount){
-        if(D) Log.d(TAG, "setVersionCounter: " + vcount);
+    private void setVersionCounter(String vcount) {
+        if (D) {
+            Log.d(TAG, "setVersionCounter: " + vcount);
+        }
         try {
             this.mVersionCounter = Long.parseLong(vcount);
         } catch (NumberFormatException e) {
@@ -97,7 +103,9 @@
     }
 
     public void setName(String name) {
-        if(D) Log.d(TAG, "setName: " + name);
+        if (D) {
+            Log.d(TAG, "setName: " + name);
+        }
         this.mName = name;
     }
 
@@ -117,17 +125,18 @@
         this.mContacts = contacts;
     }
 
-    public void addContact(BluetoothMapConvoContactElement contact){
-        if(mContacts == null)
+    public void addContact(BluetoothMapConvoContactElement contact) {
+        if (mContacts == null) {
             mContacts = new ArrayList<BluetoothMapConvoContactElement>();
+        }
         mContacts.add(contact);
     }
 
-    public void removeContact(BluetoothMapConvoContactElement contact){
+    public void removeContact(BluetoothMapConvoContactElement contact) {
         mContacts.remove(contact);
     }
 
-    public void removeContact(int index){
+    public void removeContact(int index) {
         mContacts.remove(index);
     }
 
@@ -143,11 +152,13 @@
     }
 
     public void setLastActivity(long last) {
-        if(D) Log.d(TAG, "setLastActivity: " + last);
+        if (D) {
+            Log.d(TAG, "setLastActivity: " + last);
+        }
         this.mLastActivity = last;
     }
 
-    public void setLastActivity(String lastActivity)throws ParseException {
+    public void setLastActivity(String lastActivity) throws ParseException {
         // TODO: Encode with time-zone if MCE requests it
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
         Date date = format.parse(lastActivity);
@@ -155,10 +166,10 @@
     }
 
     public String getRead() {
-        if(mReportRead == false) {
+        if (!mReportRead) {
             return "UNKNOWN";
         }
-        return (mRead?"READ":"UNREAD");
+        return (mRead ? "READ" : "UNREAD");
     }
 
     public boolean getReadBool() {
@@ -167,12 +178,14 @@
 
     public void setRead(boolean read, boolean reportRead) {
         this.mRead = read;
-        if(D) Log.d(TAG, "setRead: " + read);
+        if (D) {
+            Log.d(TAG, "setRead: " + read);
+        }
         this.mReportRead = reportRead;
     }
 
     private void setRead(String value) {
-        if(value.trim().equalsIgnoreCase("yes")) {
+        if (value.trim().equalsIgnoreCase("yes")) {
             mRead = true;
         } else {
             mRead = false;
@@ -187,11 +200,13 @@
      * @param threadId the conversation ID
      */
     public void setConvoId(long type, long threadId) {
-        this.mId = new SignedLongLong(threadId,type);
-        if(D) Log.d(TAG, "setConvoId: " + threadId + " type:" + type);
+        this.mId = new SignedLongLong(threadId, type);
+        if (D) {
+            Log.d(TAG, "setConvoId: " + threadId + " type:" + type);
+        }
     }
 
-    public String getConvoId(){
+    public String getConvoId() {
         return mId.toHexString();
     }
 
@@ -209,13 +224,13 @@
 
     /* Get a valid UTF-8 string of maximum 256 bytes */
     private String getSummary() {
-        if(mSummary != null) {
+        if (mSummary != null) {
             try {
                 return new String(BluetoothMapUtils.truncateUtf8StringToBytearray(mSummary, 256),
                         "UTF-8");
             } catch (UnsupportedEncodingException e) {
                 // This cannot happen on an Android platform - UTF-8 is mandatory
-                Log.e(TAG,"Missing UTF-8 support on platform", e);
+                Log.e(TAG, "Missing UTF-8 support on platform", e);
             }
         }
         return null;
@@ -229,6 +244,7 @@
         mSmsMmsContacts = smsMmsContacts;
     }
 
+    @Override
     public int compareTo(BluetoothMapConvoListingElement e) {
         if (this.mLastActivity < e.mLastActivity) {
             return 1;
@@ -243,37 +259,35 @@
      * Here we have taken the choice not to report empty attributes, to reduce the
      * amount of data to be transfered over BT. */
     public void encode(XmlSerializer xmlConvoElement)
-            throws IllegalArgumentException, IllegalStateException, IOException
-    {
+            throws IllegalArgumentException, IllegalStateException, IOException {
 
-            // contruct the XML tag for a single conversation in the convolisting
-            xmlConvoElement.startTag(null, XML_TAG_CONVERSATION);
-            xmlConvoElement.attribute(null, XML_ATT_ID, mId.toHexString());
-            if(mName != null) {
-                xmlConvoElement.attribute(null, XML_ATT_NAME,
-                        BluetoothMapUtils.stripInvalidChars(mName));
+        // contruct the XML tag for a single conversation in the convolisting
+        xmlConvoElement.startTag(null, XML_TAG_CONVERSATION);
+        xmlConvoElement.attribute(null, XML_ATT_ID, mId.toHexString());
+        if (mName != null) {
+            xmlConvoElement.attribute(null, XML_ATT_NAME,
+                    BluetoothMapUtils.stripInvalidChars(mName));
+        }
+        if (mLastActivity != -1) {
+            xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY, getLastActivityString());
+        }
+        // Even though this is implied, the value "UNKNOWN" kind of indicated it is required.
+        if (mReportRead) {
+            xmlConvoElement.attribute(null, XML_ATT_READ, getRead());
+        }
+        if (mVersionCounter != -1) {
+            xmlConvoElement.attribute(null, XML_ATT_VERSION_COUNTER,
+                    Long.toString(getVersionCounter()));
+        }
+        if (mSummary != null) {
+            xmlConvoElement.attribute(null, XML_ATT_SUMMARY, getSummary());
+        }
+        if (mContacts != null) {
+            for (BluetoothMapConvoContactElement contact : mContacts) {
+                contact.encode(xmlConvoElement);
             }
-            if(mLastActivity != -1) {
-                xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY,
-                        getLastActivityString());
-            }
-            // Even though this is implied, the value "UNKNOWN" kind of indicated it is required.
-            if(mReportRead == true) {
-                xmlConvoElement.attribute(null, XML_ATT_READ, getRead());
-            }
-            if(mVersionCounter != -1) {
-                xmlConvoElement.attribute(null, XML_ATT_VERSION_COUNTER,
-                        Long.toString(getVersionCounter()));
-            }
-            if(mSummary != null) {
-                xmlConvoElement.attribute(null, XML_ATT_SUMMARY, getSummary());
-            }
-            if(mContacts != null){
-                for(BluetoothMapConvoContactElement contact:mContacts){
-                   contact.encode(xmlConvoElement);
-                }
-            }
-            xmlConvoElement.endTag(null, XML_TAG_CONVERSATION);
+        }
+        xmlConvoElement.endTag(null, XML_TAG_CONVERSATION);
 
     }
 
@@ -290,39 +304,43 @@
         BluetoothMapConvoListingElement newElement = new BluetoothMapConvoListingElement();
         int count = parser.getAttributeCount();
         int type;
-        for (int i = 0; i<count; i++) {
+        for (int i = 0; i < count; i++) {
             String attributeName = parser.getAttributeName(i).trim();
             String attributeValue = parser.getAttributeValue(i);
-            if(attributeName.equalsIgnoreCase(XML_ATT_ID)) {
+            if (attributeName.equalsIgnoreCase(XML_ATT_ID)) {
                 newElement.mId = SignedLongLong.fromString(attributeValue);
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_NAME)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_NAME)) {
                 newElement.mName = attributeValue;
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) {
                 newElement.setLastActivity(attributeValue);
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_READ)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_READ)) {
                 newElement.setRead(attributeValue);
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_VERSION_COUNTER)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_VERSION_COUNTER)) {
                 newElement.setVersionCounter(attributeValue);
-            } else if(attributeName.equalsIgnoreCase(XML_ATT_SUMMARY)) {
+            } else if (attributeName.equalsIgnoreCase(XML_ATT_SUMMARY)) {
                 newElement.setSummary(attributeValue);
             } else {
-                if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
+                if (D) {
+                    Log.i(TAG, "Unknown XML attribute: " + parser.getAttributeName(i));
+                }
             }
         }
 
         // Now determine if we get an end-tag, or a new start tag for contacts
-        while((type=parser.next()) != XmlPullParser.END_TAG
-                && type != XmlPullParser.END_DOCUMENT ) {
+        while ((type = parser.next()) != XmlPullParser.END_TAG
+                && type != XmlPullParser.END_DOCUMENT) {
             // Skip until we get a start tag
             if (parser.getEventType() != XmlPullParser.START_TAG) {
                 continue;
             }
             // Skip until we get a convocontact tag
             String name = parser.getName().trim();
-            if(name.equalsIgnoreCase(BluetoothMapConvoContactElement.XML_TAG_CONVOCONTACT)){
+            if (name.equalsIgnoreCase(BluetoothMapConvoContactElement.XML_TAG_CONVOCONTACT)) {
                 newElement.addContact(BluetoothMapConvoContactElement.createFromXml(parser));
             } else {
-                if(D) Log.i(TAG,"Unknown XML tag: " + name);
+                if (D) {
+                    Log.i(TAG, "Unknown XML tag: " + name);
+                }
                 XmlUtils.skipCurrentTag(parser);
                 continue;
             }
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
index f2c6855..560558e 100644
--- a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -14,6 +14,16 @@
 */
 package com.android.bluetooth.map;
 
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringWriter;
@@ -21,22 +31,12 @@
 import java.util.HashMap;
 import java.util.Locale;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.util.Log;
-import android.util.Xml;
-
-import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
-
 
 /**
  * Class to contain a single folder element representation.
  *
  */
-public class BluetoothMapFolderElement implements Comparable<BluetoothMapFolderElement>{
+public class BluetoothMapFolderElement implements Comparable<BluetoothMapFolderElement> {
     private String mName;
     private BluetoothMapFolderElement mParent = null;
     private long mFolderId = -1;
@@ -51,9 +51,9 @@
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
 
-    private final static String TAG = "BluetoothMapFolderElement";
+    private static final String TAG = "BluetoothMapFolderElement";
 
-    public BluetoothMapFolderElement( String name, BluetoothMapFolderElement parrent){
+    public BluetoothMapFolderElement(String name, BluetoothMapFolderElement parrent) {
         this.mName = name;
         this.mParent = parrent;
         mSubFolders = new HashMap<String, BluetoothMapFolderElement>();
@@ -71,31 +71,35 @@
         return mName;
     }
 
-    public boolean hasSmsMmsContent(){
+    public boolean hasSmsMmsContent() {
         return mHasSmsMmsContent;
     }
 
-    public long getFolderId(){
+    public long getFolderId() {
         return mFolderId;
     }
-    public boolean hasEmailContent(){
+
+    public boolean hasEmailContent() {
         return mHasEmailContent;
     }
 
     public void setFolderId(long folderId) {
         this.mFolderId = folderId;
     }
+
     public void setHasSmsMmsContent(boolean hasSmsMmsContent) {
         this.mHasSmsMmsContent = hasSmsMmsContent;
     }
+
     public void setHasEmailContent(boolean hasEmailContent) {
         this.mHasEmailContent = hasEmailContent;
     }
+
     public void setHasImContent(boolean hasImContent) {
         this.mHasImContent = hasImContent;
     }
 
-    public boolean hasImContent(){
+    public boolean hasImContent() {
         return mHasImContent;
     }
 
@@ -114,8 +118,8 @@
     public String getFullPath() {
         StringBuilder sb = new StringBuilder(mName);
         BluetoothMapFolderElement current = mParent;
-        while(current != null) {
-            if(current.getParent() != null) {
+        while (current != null) {
+            if (current.getParent() != null) {
                 sb.insert(0, current.mName + "/");
             }
             current = current.getParent();
@@ -130,8 +134,9 @@
         folderElement = folderElement.getSubFolder("telecom");
         folderElement = folderElement.getSubFolder("msg");
         folderElement = folderElement.getSubFolder(name);
-        if (folderElement != null && folderElement.getFolderId() == -1 )
+        if (folderElement != null && folderElement.getFolderId() == -1) {
             folderElement = null;
+        }
         return folderElement;
     }
 
@@ -141,7 +146,7 @@
 
     public static BluetoothMapFolderElement getFolderById(long id,
             BluetoothMapFolderElement folderStructure) {
-        if(folderStructure == null) {
+        if (folderStructure == null) {
             return null;
         }
         return findFolderById(id, folderStructure.getRoot());
@@ -149,15 +154,14 @@
 
     private static BluetoothMapFolderElement findFolderById(long id,
             BluetoothMapFolderElement folder) {
-        if(folder.getFolderId() == id) {
+        if (folder.getFolderId() == id) {
             return folder;
         }
         /* Else */
-        for(BluetoothMapFolderElement subFolder : folder.mSubFolders.values().toArray(
-                new BluetoothMapFolderElement[folder.mSubFolders.size()]))
-        {
+        for (BluetoothMapFolderElement subFolder : folder.mSubFolders.values()
+                .toArray(new BluetoothMapFolderElement[folder.mSubFolders.size()])) {
             BluetoothMapFolderElement ret = findFolderById(id, subFolder);
-            if(ret != null) {
+            if (ret != null) {
                 return ret;
             }
         }
@@ -171,8 +175,9 @@
      */
     public BluetoothMapFolderElement getRoot() {
         BluetoothMapFolderElement rootFolder = this;
-        while(rootFolder.getParent() != null)
+        while (rootFolder.getParent() != null) {
             rootFolder = rootFolder.getParent();
+        }
         return rootFolder;
     }
 
@@ -181,15 +186,19 @@
      * @param name the name of the folder to add.
      * @return the added folder element.
      */
-    public BluetoothMapFolderElement addFolder(String name){
+    public BluetoothMapFolderElement addFolder(String name) {
         name = name.toLowerCase(Locale.US);
         BluetoothMapFolderElement newFolder = mSubFolders.get(name);
-        if(newFolder == null) {
-            if(D) Log.i(TAG,"addFolder():" + name);
+        if (newFolder == null) {
+            if (D) {
+                Log.i(TAG, "addFolder():" + name);
+            }
             newFolder = new BluetoothMapFolderElement(name, this);
             mSubFolders.put(name, newFolder);
         } else {
-            if(D) Log.i(TAG,"addFolder():" + name + " already added");
+            if (D) {
+                Log.i(TAG, "addFolder():" + name + " already added");
+            }
         }
         return newFolder;
     }
@@ -199,8 +208,10 @@
      * @param name the name of the folder to add.
      * @return the added folder element.
      */
-    public BluetoothMapFolderElement addSmsMmsFolder(String name){
-        if(D) Log.i(TAG,"addSmsMmsFolder()");
+    public BluetoothMapFolderElement addSmsMmsFolder(String name) {
+        if (D) {
+            Log.i(TAG, "addSmsMmsFolder()");
+        }
         BluetoothMapFolderElement newFolder = addFolder(name);
         newFolder.setHasSmsMmsContent(true);
         return newFolder;
@@ -211,8 +222,10 @@
      * @param name the name of the folder to add.
      * @return the added folder element.
      */
-    public BluetoothMapFolderElement addImFolder(String name, long idFolder){
-        if(D) Log.i(TAG,"addImFolder() id = " + idFolder);
+    public BluetoothMapFolderElement addImFolder(String name, long idFolder) {
+        if (D) {
+            Log.i(TAG, "addImFolder() id = " + idFolder);
+        }
         BluetoothMapFolderElement newFolder = addFolder(name);
         newFolder.setHasImContent(true);
         newFolder.setFolderId(idFolder);
@@ -224,18 +237,21 @@
      * @param name the name of the folder to add.
      * @return the added folder element.
      */
-    public BluetoothMapFolderElement addEmailFolder(String name, long emailFolderId){
-        if(V) Log.v(TAG,"addEmailFolder() id = " + emailFolderId);
+    public BluetoothMapFolderElement addEmailFolder(String name, long emailFolderId) {
+        if (V) {
+            Log.v(TAG, "addEmailFolder() id = " + emailFolderId);
+        }
         BluetoothMapFolderElement newFolder = addFolder(name);
         newFolder.setFolderId(emailFolderId);
         newFolder.setHasEmailContent(true);
         return newFolder;
     }
+
     /**
      * Fetch the number of sub folders.
      * @return returns the number of sub folders.
      */
-    public int getSubFolderCount(){
+    public int getSubFolderCount() {
         return mSubFolders.size();
     }
 
@@ -244,7 +260,7 @@
      * @param folderName the name of the subFolder to find.
      * @return the subFolder element if found {@code null} otherwise.
      */
-    public BluetoothMapFolderElement getSubFolder(String folderName){
+    public BluetoothMapFolderElement getSubFolder(String folderName) {
         return mSubFolders.get(folderName.toLowerCase());
     }
 
@@ -253,14 +269,17 @@
         XmlSerializer xmlMsgElement = new FastXmlSerializer();
         int i, stopIndex;
         // We need index based access to the subFolders
-        BluetoothMapFolderElement[] folders = mSubFolders.values().toArray(new BluetoothMapFolderElement[mSubFolders.size()]);
+        BluetoothMapFolderElement[] folders =
+                mSubFolders.values().toArray(new BluetoothMapFolderElement[mSubFolders.size()]);
 
-        if(offset > mSubFolders.size())
+        if (offset > mSubFolders.size()) {
             throw new IllegalArgumentException("FolderListingEncode: offset > subFolders.size()");
+        }
 
         stopIndex = offset + count;
-        if(stopIndex > mSubFolders.size())
+        if (stopIndex > mSubFolders.size()) {
             stopIndex = mSubFolders.size();
+        }
 
         try {
             xmlMsgElement.setOutput(sw);
@@ -268,8 +287,7 @@
             xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
             xmlMsgElement.startTag(null, "folder-listing");
             xmlMsgElement.attribute(null, "version", BluetoothMapUtils.MAP_V10_STR);
-            for(i = offset; i<stopIndex; i++)
-            {
+            for (i = offset; i < stopIndex; i++) {
                 xmlMsgElement.startTag(null, "folder");
                 xmlMsgElement.attribute(null, "name", folders[i].getName());
                 xmlMsgElement.endTag(null, "folder");
@@ -277,13 +295,19 @@
             xmlMsgElement.endTag(null, "folder-listing");
             xmlMsgElement.endDocument();
         } catch (IllegalArgumentException e) {
-            if(D) Log.w(TAG,e);
+            if (D) {
+                Log.w(TAG, e);
+            }
             throw new IllegalArgumentException("error encoding folderElement");
         } catch (IllegalStateException e) {
-            if(D) Log.w(TAG,e);
+            if (D) {
+                Log.w(TAG, e);
+            }
             throw new IllegalArgumentException("error encoding folderElement");
         } catch (IOException e) {
-            if(D) Log.w(TAG,e);
+            if (D) {
+                Log.w(TAG, e);
+            }
             throw new IllegalArgumentException("error encoding folderElement");
         }
         return sw.toString().getBytes("UTF-8");
@@ -310,16 +334,18 @@
             parser.setInput(xmlDocument, "UTF-8");
 
             // First find the folder-listing
-            while((type=parser.next()) != XmlPullParser.END_TAG
-                    && type != XmlPullParser.END_DOCUMENT ) {
+            while ((type = parser.next()) != XmlPullParser.END_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
                 // Skip until we get a start tag
                 if (parser.getEventType() != XmlPullParser.START_TAG) {
                     continue;
                 }
                 // Skip until we get a folder-listing tag
                 String name = parser.getName();
-                if(!name.equalsIgnoreCase("folder-listing")) {
-                    if(D) Log.i(TAG,"Unknown XML tag: " + name);
+                if (!name.equalsIgnoreCase("folder-listing")) {
+                    if (D) {
+                        Log.i(TAG, "Unknown XML tag: " + name);
+                    }
                     XmlUtils.skipCurrentTag(parser);
                 }
                 readFolders(parser);
@@ -335,26 +361,29 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    public void readFolders(XmlPullParser parser)
-            throws XmlPullParserException, IOException {
+    public void readFolders(XmlPullParser parser) throws XmlPullParserException, IOException {
         int type;
-        if(D) Log.i(TAG,"readFolders(): ");
-        while((type=parser.next()) != XmlPullParser.END_TAG
-                && type != XmlPullParser.END_DOCUMENT ) {
+        if (D) {
+            Log.i(TAG, "readFolders(): ");
+        }
+        while ((type = parser.next()) != XmlPullParser.END_TAG
+                && type != XmlPullParser.END_DOCUMENT) {
             // Skip until we get a start tag
             if (parser.getEventType() != XmlPullParser.START_TAG) {
                 continue;
             }
             // Skip until we get a folder-listing tag
             String name = parser.getName();
-            if(name.trim().equalsIgnoreCase("folder") == false) {
-                if(D) Log.i(TAG,"Unknown XML tag: " + name);
+            if (!name.trim().equalsIgnoreCase("folder")) {
+                if (D) {
+                    Log.i(TAG, "Unknown XML tag: " + name);
+                }
                 XmlUtils.skipCurrentTag(parser);
                 continue;
             }
             int count = parser.getAttributeCount();
-            for (int i = 0; i<count; i++) {
-                if(parser.getAttributeName(i).trim().equalsIgnoreCase("name")) {
+            for (int i = 0; i < count; i++) {
+                if (parser.getAttributeName(i).trim().equalsIgnoreCase("name")) {
                     // We found a folder, append to sub folders.
                     BluetoothMapFolderElement element =
                             addFolder(parser.getAttributeValue(i).trim());
@@ -362,7 +391,9 @@
                     element.setHasImContent(mHasImContent);
                     element.setHasSmsMmsContent(mHasSmsMmsContent);
                 } else {
-                    if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
+                    if (D) {
+                        Log.i(TAG, "Unknown XML attribute: " + parser.getAttributeName(i));
+                    }
                 }
             }
             parser.nextTag();
@@ -374,32 +405,42 @@
      */
     @Override
     public int compareTo(BluetoothMapFolderElement another) {
-        if(another == null) return 1;
+        if (another == null) {
+            return 1;
+        }
         int ret = mName.compareToIgnoreCase(another.mName);
         // TODO: Do we want to add compare of folder type?
-        if(ret == 0) {
+        if (ret == 0) {
             ret = mSubFolders.size() - another.mSubFolders.size();
-            if(ret == 0) {
+            if (ret == 0) {
                 // Compare all sub folder elements (will do nothing if mSubFolders is empty)
-                for(BluetoothMapFolderElement subfolder : mSubFolders.values()) {
+                for (BluetoothMapFolderElement subfolder : mSubFolders.values()) {
                     BluetoothMapFolderElement subfolderAnother =
                             another.mSubFolders.get(subfolder.getName());
-                    if(subfolderAnother == null) {
-                        if(D) Log.i(TAG, subfolder.getFullPath() + " not in another");
+                    if (subfolderAnother == null) {
+                        if (D) {
+                            Log.i(TAG, subfolder.getFullPath() + " not in another");
+                        }
                         return 1;
                     }
                     ret = subfolder.compareTo(subfolderAnother);
-                    if(ret != 0) {
-                        if(D) Log.i(TAG, subfolder.getFullPath() + " filed compareTo()");
+                    if (ret != 0) {
+                        if (D) {
+                            Log.i(TAG, subfolder.getFullPath() + " filed compareTo()");
+                        }
                         return ret;
                     }
                 }
             } else {
-                if(D) Log.i(TAG, "mSubFolders.size(): " + mSubFolders.size() +
-                        " another.mSubFolders.size(): " + another.mSubFolders.size());
+                if (D) {
+                    Log.i(TAG, "mSubFolders.size(): " + mSubFolders.size()
+                            + " another.mSubFolders.size(): " + another.mSubFolders.size());
+                }
             }
         } else {
-            if(D) Log.i(TAG, "mName: " + mName + " another.mName: " + another.mName);
+            if (D) {
+                Log.i(TAG, "mName: " + mName + " another.mName: " + another.mName);
+            }
         }
         return ret;
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index 3d32382..e3df91f 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -14,21 +14,6 @@
 */
 package com.android.bluetooth.map;
 
-import java.io.IOException;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-
-import javax.obex.ServerSession;
-
-import com.android.bluetooth.BluetoothObexTransport;
-import com.android.bluetooth.IObexConnectionHandler;
-import com.android.bluetooth.ObexServerSockets;
-import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
-import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
-import com.android.bluetooth.sdp.SdpManager;
-
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothSocket;
@@ -38,23 +23,38 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothObexTransport;
+import com.android.bluetooth.IObexConnectionHandler;
+import com.android.bluetooth.ObexServerSockets;
+import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.sdp.SdpManager;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.obex.ServerSession;
+
 public class BluetoothMapMasInstance implements IObexConnectionHandler {
-    private final String TAG;
+    private final String mTag;
     private static volatile int sInstanceCounter = 0;
 
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
 
-    private static final int SDP_MAP_MSG_TYPE_EMAIL    = 0x01;
-    private static final int SDP_MAP_MSG_TYPE_SMS_GSM  = 0x02;
+    private static final int SDP_MAP_MSG_TYPE_EMAIL = 0x01;
+    private static final int SDP_MAP_MSG_TYPE_SMS_GSM = 0x02;
     private static final int SDP_MAP_MSG_TYPE_SMS_CDMA = 0x04;
-    private static final int SDP_MAP_MSG_TYPE_MMS      = 0x08;
-    private static final int SDP_MAP_MSG_TYPE_IM       = 0x10;
+    private static final int SDP_MAP_MSG_TYPE_MMS = 0x08;
+    private static final int SDP_MAP_MSG_TYPE_IM = 0x10;
 
-    private static final int SDP_MAP_MAS_VERSION       = 0x0102;
+    private static final int SDP_MAP_MAS_VERSION = 0x0102;
 
     /* TODO: Should these be adaptive for each MAS? - e.g. read from app? */
-    private static final int SDP_MAP_MAS_FEATURES      = 0x0000007F;
+    static final int SDP_MAP_MAS_FEATURES = 0x0000007F;
 
     private ServerSession mServerSession = null;
     // The handle to the socket registration with SDP
@@ -69,6 +69,7 @@
 
     private volatile boolean mInterrupted;              // Used to interrupt socket accept thread
     private volatile boolean mShutdown = false;         // Used to interrupt socket accept thread
+    private volatile boolean mAcceptNewConnections = false;
 
     private Handler mServiceHandler = null;             // MAP service message handler
     private BluetoothMapService mMapService = null;     // Handle to the outer MAP service
@@ -85,16 +86,16 @@
     private AtomicLong mSmsMmsConvoListVersionCounter = new AtomicLong(0);
     private AtomicLong mImEmailConvoListVersionCounter = new AtomicLong(0);
 
-    private Map<Long, Msg> mMsgListSms=null;
-    private Map<Long, Msg> mMsgListMms=null;
-    private Map<Long, Msg> mMsgListMsg=null;
+    private Map<Long, Msg> mMsgListSms = null;
+    private Map<Long, Msg> mMsgListMms = null;
+    private Map<Long, Msg> mMsgListMsg = null;
 
     private Map<String, BluetoothMapConvoContactElement> mContactList;
 
-    private HashMap<Long,BluetoothMapConvoListingElement> mSmsMmsConvoList =
+    private HashMap<Long, BluetoothMapConvoListingElement> mSmsMmsConvoList =
             new HashMap<Long, BluetoothMapConvoListingElement>();
 
-    private HashMap<Long,BluetoothMapConvoListingElement> mImEmailConvoList =
+    private HashMap<Long, BluetoothMapConvoListingElement> mImEmailConvoList =
             new HashMap<Long, BluetoothMapConvoListingElement>();
 
     private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
@@ -110,17 +111,14 @@
      * @param mns
      * @param emailBaseUri - use null to create a SMS/MMS MAS instance
      */
-    public BluetoothMapMasInstance (BluetoothMapService mapService,
-            Context context,
-            BluetoothMapAccountItem account,
-            int masId,
-            boolean enableSmsMms) {
-        TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
+    public BluetoothMapMasInstance(BluetoothMapService mapService, Context context,
+            BluetoothMapAccountItem account, int masId, boolean enableSmsMms) {
+        mTag = "BluetoothMapMasInstance" + sInstanceCounter++;
         mMapService = mapService;
         mServiceHandler = mapService.getHandler();
         mContext = context;
         mAccount = account;
-        if(account != null) {
+        if (account != null) {
             mBaseUri = account.mBase_uri;
         }
         mMasInstanceId = masId;
@@ -129,19 +127,20 @@
     }
 
     private void removeSdpRecord() {
-        if (mAdapter != null && mSdpHandle >= 0 &&
-                SdpManager.getDefaultManager() != null) {
-            if (V) Log.d(TAG, "Removing SDP record for MAS instance: " + mMasInstanceId +
-                    " Object reference: " + this + "SDP handle: " + mSdpHandle);
+        if (mAdapter != null && mSdpHandle >= 0 && SdpManager.getDefaultManager() != null) {
+            if (V) {
+                Log.d(mTag, "Removing SDP record for MAS instance: " + mMasInstanceId
+                        + " Object reference: " + this + "SDP handle: " + mSdpHandle);
+            }
             boolean status = SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
-            Log.d(TAG, "RemoveSDPrecord returns " + status);
+            Log.d(mTag, "RemoveSDPrecord returns " + status);
             mSdpHandle = -1;
         }
     }
 
     /* Needed only for test */
     protected BluetoothMapMasInstance() {
-        TAG = "BluetoothMapMasInstance" + sInstanceCounter++;
+        mTag = "BluetoothMapMasInstance" + sInstanceCounter++;
     }
 
     @Override
@@ -163,7 +162,7 @@
      * - If a MAS instance folderVersionCounter roles over - will not happen before a long
      *   is too small to hold a unix time-stamp, hence is not handled.
      */
-    private void updateDbIdentifier(){
+    private void updateDbIdentifier() {
         mDbIndetifier.set(Calendar.getInstance().getTime().getTime());
     }
 
@@ -221,19 +220,19 @@
         mContactList = contactList;
     }
 
-    HashMap<Long,BluetoothMapConvoListingElement> getSmsMmsConvoList() {
+    HashMap<Long, BluetoothMapConvoListingElement> getSmsMmsConvoList() {
         return mSmsMmsConvoList;
     }
 
-    void setSmsMmsConvoList(HashMap<Long,BluetoothMapConvoListingElement> smsMmsConvoList) {
+    void setSmsMmsConvoList(HashMap<Long, BluetoothMapConvoListingElement> smsMmsConvoList) {
         mSmsMmsConvoList = smsMmsConvoList;
     }
 
-    HashMap<Long,BluetoothMapConvoListingElement> getImEmailConvoList() {
+    HashMap<Long, BluetoothMapConvoListingElement> getImEmailConvoList() {
         return mImEmailConvoList;
     }
 
-    void setImEmailConvoList(HashMap<Long,BluetoothMapConvoListingElement> imEmailConvoList) {
+    void setImEmailConvoList(HashMap<Long, BluetoothMapConvoListingElement> imEmailConvoList) {
         mImEmailConvoList = imEmailConvoList;
     }
 
@@ -259,39 +258,51 @@
         return combinedVersionCounter;
     }
 
-    synchronized public void startRfcommSocketListener() {
-        if (D) Log.d(TAG, "Map Service startRfcommSocketListener");
+    /**
+     * Start Obex Server Sockets and create the SDP record.
+     */
+    public synchronized void startSocketListeners() {
+        if (D) {
+            Log.d(mTag, "Map Service startSocketListeners");
+        }
 
         if (mServerSession != null) {
-            if (D) Log.d(TAG, "mServerSession exists - shutting it down...");
+            if (D) {
+                Log.d(mTag, "mServerSession exists - shutting it down...");
+            }
             mServerSession.close();
             mServerSession = null;
         }
         if (mObserver != null) {
-            if (D) Log.d(TAG, "mObserver exists - shutting it down...");
+            if (D) {
+                Log.d(mTag, "mObserver exists - shutting it down...");
+            }
             mObserver.deinit();
             mObserver = null;
         }
 
         closeConnectionSocket();
 
-        if(mServerSockets != null) {
-            mServerSockets.prepareForNewConnect();
+        if (mServerSockets != null) {
+            mAcceptNewConnections = true;
         } else {
 
             mServerSockets = ObexServerSockets.create(this);
+            mAcceptNewConnections = true;
 
-            if(mServerSockets == null) {
+            if (mServerSockets == null) {
                 // TODO: Handle - was not handled before
-                Log.e(TAG, "Failed to start the listeners");
+                Log.e(mTag, "Failed to start the listeners");
                 return;
             }
             removeSdpRecord();
             mSdpHandle = createMasSdpRecord(mServerSockets.getRfcommChannel(),
                     mServerSockets.getL2capPsm());
             // Here we might have changed crucial data, hence reset DB identifier
-            if(V) Log.d(TAG, "Creating new SDP record for MAS instance: " + mMasInstanceId +
-                    " Object reference: " + this + "SDP handle: " + mSdpHandle);
+            if (V) {
+                Log.d(mTag, "Creating new SDP record for MAS instance: " + mMasInstanceId
+                        + " Object reference: " + this + "SDP handle: " + mSdpHandle);
+            }
             updateDbIdentifier();
         }
     }
@@ -304,38 +315,33 @@
     private int createMasSdpRecord(int rfcommChannel, int l2capPsm) {
         String masName = "";
         int messageTypeFlags = 0;
-        if(mEnableSmsMms) {
+        if (mEnableSmsMms) {
             masName = TYPE_SMS_MMS_STR;
-            messageTypeFlags |= SDP_MAP_MSG_TYPE_SMS_GSM |
-                           SDP_MAP_MSG_TYPE_SMS_CDMA|
-                           SDP_MAP_MSG_TYPE_MMS;
+            messageTypeFlags |=
+                    SDP_MAP_MSG_TYPE_SMS_GSM | SDP_MAP_MSG_TYPE_SMS_CDMA | SDP_MAP_MSG_TYPE_MMS;
         }
 
-        if(mBaseUri != null) {
-            if(mEnableSmsMms) {
-                if(mAccount.getType() == TYPE.EMAIL) {
+        if (mBaseUri != null) {
+            if (mEnableSmsMms) {
+                if (mAccount.getType() == TYPE.EMAIL) {
                     masName += "/" + TYPE_EMAIL_STR;
-                } else if(mAccount.getType() == TYPE.IM) {
+                } else if (mAccount.getType() == TYPE.IM) {
                     masName += "/" + TYPE_IM_STR;
                 }
             } else {
                 masName = mAccount.getName();
             }
 
-            if(mAccount.getType() == TYPE.EMAIL) {
+            if (mAccount.getType() == TYPE.EMAIL) {
                 messageTypeFlags |= SDP_MAP_MSG_TYPE_EMAIL;
-            } else if(mAccount.getType() == TYPE.IM) {
+            } else if (mAccount.getType() == TYPE.IM) {
                 messageTypeFlags |= SDP_MAP_MSG_TYPE_IM;
             }
         }
 
-        return SdpManager.getDefaultManager().createMapMasRecord(masName,
-                mMasInstanceId,
-                rfcommChannel,
-                l2capPsm,
-                SDP_MAP_MAS_VERSION,
-                messageTypeFlags,
-                SDP_MAP_MAS_FEATURES);
+        return SdpManager.getDefaultManager()
+                .createMapMasRecord(masName, mMasInstanceId, rfcommChannel, l2capPsm,
+                        SDP_MAP_MAS_VERSION, messageTypeFlags, SDP_MAP_MAS_FEATURES);
     }
 
     /* Called for all MAS instances for each instance when auth. is completed, hence
@@ -343,42 +349,42 @@
      * Returns true at success. */
     public boolean startObexServerSession(BluetoothMnsObexClient mnsClient)
             throws IOException, RemoteException {
-        if (D) Log.d(TAG, "Map Service startObexServerSession masid = " + mMasInstanceId);
+        if (D) {
+            Log.d(mTag, "Map Service startObexServerSession masid = " + mMasInstanceId);
+        }
 
         if (mConnSocket != null) {
-            if(mServerSession != null) {
+            if (mServerSession != null) {
                 // Already connected, just return true
                 return true;
             }
 
             mMnsClient = mnsClient;
             BluetoothMapObexServer mapServer;
-            mObserver = new  BluetoothMapContentObserver(mContext,
-                                                         mMnsClient,
-                                                         this,
-                                                         mAccount,
-                                                         mEnableSmsMms);
+            mObserver = new BluetoothMapContentObserver(mContext, mMnsClient, this, mAccount,
+                    mEnableSmsMms);
             mObserver.init();
-            mapServer = new BluetoothMapObexServer(mServiceHandler,
-                                                    mContext,
-                                                    mObserver,
-                                                    this,
-                                                    mAccount,
-                                                    mEnableSmsMms);
+            mapServer =
+                    new BluetoothMapObexServer(mServiceHandler, mContext, mObserver, this, mAccount,
+                            mEnableSmsMms);
 
             // setup transport
             BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
             mServerSession = new ServerSession(transport, mapServer, null);
-            if (D) Log.d(TAG, "    ServerSession started.");
+            if (D) {
+                Log.d(mTag, "    ServerSession started.");
+            }
 
             return true;
         }
-        if (D) Log.d(TAG, "    No connection for this instance");
+        if (D) {
+            Log.d(mTag, "    No connection for this instance");
+        }
         return false;
     }
 
-    public boolean handleSmsSendIntent(Context context, Intent intent){
-        if(mObserver != null) {
+    public boolean handleSmsSendIntent(Context context, Intent intent) {
+        if (mObserver != null) {
             return mObserver.handleSmsSendIntent(context, intent);
         }
         return false;
@@ -393,7 +399,9 @@
     }
 
     public void shutdown() {
-        if (D) Log.d(TAG, "MAP Service shutdown");
+        if (D) {
+            Log.d(mTag, "MAP Service shutdown");
+        }
 
         if (mServerSession != null) {
             mServerSession.close();
@@ -407,20 +415,23 @@
         removeSdpRecord();
 
         closeConnectionSocket();
-
-        closeServerSockets(true);
+        // Do not block for Accept thread cleanup.
+        // Fix Handler Thread block during BT Turn OFF.
+        closeServerSockets(false);
     }
 
     /**
      * Signal to the ServerSockets handler that a new connection may be accepted.
      */
     public void restartObexServerSession() {
-        if (D) Log.d(TAG, "MAP Service restartObexServerSession()");
-        startRfcommSocketListener();
+        if (D) {
+            Log.d(mTag, "MAP Service restartObexServerSession()");
+        }
+        startSocketListeners();
     }
 
 
-    private final synchronized void closeServerSockets(boolean block) {
+    private synchronized void closeServerSockets(boolean block) {
         // exit SocketAcceptThread early
         ObexServerSockets sockets = mServerSockets;
         if (sockets != null) {
@@ -429,12 +440,12 @@
         }
     }
 
-    private final synchronized void closeConnectionSocket() {
+    private synchronized void closeConnectionSocket() {
         if (mConnSocket != null) {
             try {
                 mConnSocket.close();
             } catch (IOException e) {
-                Log.e(TAG, "Close Connection Socket error: ", e);
+                Log.e(mTag, "Close Connection Socket error: ", e);
             } finally {
                 mConnSocket = null;
             }
@@ -442,27 +453,35 @@
     }
 
     public void setRemoteFeatureMask(int supportedFeatures) {
-       if(V) Log.v(TAG, "setRemoteFeatureMask : Curr: "+ mRemoteFeatureMask);
-       mRemoteFeatureMask  = supportedFeatures;
-       if (mObserver != null) {
-           mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
-           if(V) Log.v(TAG, "setRemoteFeatureMask : set: " + mRemoteFeatureMask);
-       }
+        if (V) {
+            Log.v(mTag, "setRemoteFeatureMask : Curr: " + mRemoteFeatureMask);
+        }
+        mRemoteFeatureMask = supportedFeatures & SDP_MAP_MAS_FEATURES;
+        if (mObserver != null) {
+            mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
+            if (V) {
+                Log.v(mTag, "setRemoteFeatureMask : set: " + mRemoteFeatureMask);
+            }
+        }
     }
 
-    public int getRemoteFeatureMask(){
+    public int getRemoteFeatureMask() {
         return this.mRemoteFeatureMask;
     }
 
     @Override
     public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
+        if (!mAcceptNewConnections) {
+            return false;
+        }
         /* Signal to the service that we have received an incoming connection.
          */
         boolean isValid = mMapService.onConnect(device, BluetoothMapMasInstance.this);
 
-        if(isValid == true) {
+        if (isValid) {
             mRemoteDevice = device;
             mConnSocket = socket;
+            mAcceptNewConnections = false;
         }
 
         return isValid;
@@ -476,11 +495,11 @@
     @Override
     public synchronized void onAcceptFailed() {
         mServerSockets = null; // Will cause a new to be created when calling start.
-        if(mShutdown) {
-            Log.e(TAG,"Failed to accept incomming connection - " + "shutdown");
+        if (mShutdown) {
+            Log.e(mTag, "Failed to accept incomming connection - " + "shutdown");
         } else {
-            Log.e(TAG,"Failed to accept incomming connection - " + "restarting");
-            startRfcommSocketListener();
+            Log.e(mTag, "Failed to accept incomming connection - " + "restarting");
+            startSocketListeners();
         }
     }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
index a4bc9e3..dbe3b8c 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
@@ -14,34 +14,37 @@
 */
 package com.android.bluetooth.map;
 
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.bluetooth.DeviceWorkArounds;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import com.android.internal.util.FastXmlSerializer;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import android.util.Log;
 
 public class BluetoothMapMessageListing {
-    private boolean hasUnread = false;
+    private boolean mHasUnread = false;
     private static final String TAG = "BluetoothMapMessageListing";
     private static final boolean D = BluetoothMapService.DEBUG;
 
     private List<BluetoothMapMessageListingElement> mList;
 
-    public BluetoothMapMessageListing(){
+    public BluetoothMapMessageListing() {
         mList = new ArrayList<BluetoothMapMessageListingElement>();
     }
+
     public void add(BluetoothMapMessageListingElement element) {
         mList.add(element);
         /* update info regarding whether the list contains unread messages */
-        if (element.getReadBool())
-        {
-            hasUnread = true;
+        if (!element.getReadBool()) {
+            mHasUnread = true;
         }
     }
 
@@ -50,8 +53,7 @@
      * @return the number of elements in the list.
      */
     public int getCount() {
-        if(mList != null)
-        {
+        if (mList != null) {
             return mList.size();
         }
         return 0;
@@ -61,9 +63,8 @@
      * does the list contain any unread messages
      * @return true if unread messages have been added to the list, else false
      */
-    public boolean hasUnread()
-    {
-        return hasUnread;
+    public boolean hasUnread() {
+        return mHasUnread;
     }
 
 
@@ -71,7 +72,7 @@
      *  returns the entire list as a list
      * @return list
      */
-    public List<BluetoothMapMessageListingElement> getList(){
+    public List<BluetoothMapMessageListingElement> getList() {
         return mList;
     }
 
@@ -87,13 +88,27 @@
      *             if UTF-8 encoding is unsupported on the platform.
      */
     // TODO: Remove includeThreadId when MAP-IM is adopted
-    public byte[] encode(boolean includeThreadId, String version) throws UnsupportedEncodingException {
+    public byte[] encode(boolean includeThreadId, String version)
+            throws UnsupportedEncodingException {
         StringWriter sw = new StringWriter();
-        XmlSerializer xmlMsgElement = new FastXmlSerializer();
+        XmlSerializer xmlMsgElement = null;
+        boolean isBenzCarkit = DeviceWorkArounds.addressStartsWith(
+                BluetoothMapService.getRemoteDevice().getAddress(),
+                DeviceWorkArounds.MERCEDES_BENZ_CARKIT);
         try {
-            xmlMsgElement.setOutput(sw);
-            xmlMsgElement.startDocument("UTF-8", true);
-            xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            if (isBenzCarkit) {
+                Log.d(TAG, "java_interop: Remote is Mercedes Benz, "
+                        + "using Xml Workaround.");
+                xmlMsgElement = Xml.newSerializer();
+                xmlMsgElement.setOutput(sw);
+                xmlMsgElement.text("\n");
+            } else {
+                xmlMsgElement = new FastXmlSerializer();
+                xmlMsgElement.setOutput(sw);
+                xmlMsgElement.startDocument("UTF-8", true);
+                xmlMsgElement.setFeature(
+                        "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            }
             xmlMsgElement.startTag(null, "MAP-msg-listing");
             xmlMsgElement.attribute(null, "version", version);
             // Do the XML encoding of list
@@ -109,6 +124,15 @@
         } catch (IOException e) {
             Log.w(TAG, e);
         }
+        /* Fix IOT issue to replace '&amp;' by '&', &lt; by < and '&gt; by '>' in MessageListing */
+        if (DeviceWorkArounds.addressStartsWith(BluetoothMapService.getRemoteDevice().getAddress(),
+                    DeviceWorkArounds.BREZZA_ZDI_CARKIT)) {
+            return sw.toString()
+                    .replaceAll("&amp;", "&")
+                    .replaceAll("&lt;", "<")
+                    .replaceAll("&gt;", ">")
+                    .getBytes("UTF-8");
+        }
         return sw.toString().getBytes("UTF-8");
     }
 
@@ -120,15 +144,15 @@
         count = Math.min(count, mList.size() - offset);
         if (count > 0) {
             mList = mList.subList(offset, offset + count);
-            if(mList == null) {
+            if (mList == null) {
                 mList = new ArrayList<BluetoothMapMessageListingElement>(); // Return an empty list
             }
         } else {
-            if(offset > mList.size()) {
-               mList = new ArrayList<BluetoothMapMessageListingElement>();
-               Log.d(TAG, "offset greater than list size. Returning empty list");
+            if (offset > mList.size()) {
+                mList = new ArrayList<BluetoothMapMessageListingElement>();
+                Log.d(TAG, "offset greater than list size. Returning empty list");
             } else {
-               mList = mList.subList(offset, mList.size());
+                mList = mList.subList(offset, mList.size());
             }
         }
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
index eb9343e..17fc549 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
@@ -14,18 +14,17 @@
 */
 package com.android.bluetooth.map;
 
-import java.io.IOException;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import com.android.bluetooth.DeviceWorkArounds;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 
 import org.xmlpull.v1.XmlSerializer;
 
-import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
-import com.android.bluetooth.util.Interop;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 
 public class BluetoothMapMessageListingElement
-    implements Comparable<BluetoothMapMessageListingElement> {
+        implements Comparable<BluetoothMapMessageListingElement> {
 
     private static final String TAG = "BluetoothMapMessageListingElement";
     private static final boolean D = false;
@@ -204,11 +203,13 @@
     }
 
     public String getRead() {
-        return (mRead?"yes":"no");
+        return (mRead ? "yes" : "no");
     }
+
     public boolean getReadBool() {
         return mRead;
     }
+
     public void setRead(boolean read, boolean reportRead) {
         this.mRead = read;
         this.mReportRead = reportRead;
@@ -231,7 +232,7 @@
     }
 
     public void setThreadId(long threadId, TYPE type) {
-        if(threadId != -1) {
+        if (threadId != -1) {
             this.mThreadId = BluetoothMapUtils.getMapConvoHandle(threadId, type);
         }
     }
@@ -252,6 +253,7 @@
         this.mFolderType = folderType;
     }
 
+    @Override
     public int compareTo(BluetoothMapMessageListingElement e) {
         if (this.mDateTime < e.mDateTime) {
             return 1;
@@ -265,70 +267,90 @@
     /* Encode the MapMessageListingElement into the StringBuilder reference.
      * */
     public void encode(XmlSerializer xmlMsgElement, boolean includeThreadId)
-            throws IllegalArgumentException, IllegalStateException, IOException
-    {
-            // contruct the XML tag for a single msg in the msglisting
-            xmlMsgElement.startTag(null, "msg");
-            xmlMsgElement.attribute(null, "handle",
-                    BluetoothMapUtils.getMapHandle(mCpHandle, mType));
-            if(mSubject != null){
-                String stripped = BluetoothMapUtils.stripInvalidChars(mSubject);
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        // contruct the XML tag for a single msg in the msglisting
+        xmlMsgElement.startTag(null, "msg");
+        xmlMsgElement.attribute(null, "handle", BluetoothMapUtils.getMapHandle(mCpHandle, mType));
+        if (mSubject != null) {
+            String stripped = BluetoothMapUtils.stripInvalidChars(mSubject);
 
-                if (Interop.matchByAddress(Interop.INTEROP_MAP_ASCIIONLY,
-                        BluetoothMapService.getRemoteDevice().getAddress())) {
-                    stripped = stripped.replaceAll("[\\P{ASCII}&\"><]", "");
-                    if (stripped.isEmpty()) stripped = "---";
+            if (DeviceWorkArounds.addressStartsWith(BluetoothMapService
+                    .getRemoteDevice().getAddress(), DeviceWorkArounds
+                    .MERCEDES_BENZ_CARKIT)) {
+                stripped = stripped.replaceAll("[\\P{ASCII}&\"><]", "");
+                if (stripped.isEmpty()) {
+                    stripped = "---";
                 }
-
-                xmlMsgElement.attribute(null, "subject",
-                        stripped.substring(0,  stripped.length() < 256 ? stripped.length() : 256));
             }
 
-            if(mDateTime != 0)
-                xmlMsgElement.attribute(null, "datetime", this.getDateTimeString());
-            if(mSenderName != null)
-                xmlMsgElement.attribute(null, "sender_name",
-                        BluetoothMapUtils.stripInvalidChars(mSenderName));
-            if(mSenderAddressing != null)
-                xmlMsgElement.attribute(null, "sender_addressing", mSenderAddressing);
-            if(mReplytoAddressing != null)
-                xmlMsgElement.attribute(null, "replyto_addressing",mReplytoAddressing);
-            if(mRecipientName != null)
-                xmlMsgElement.attribute(null, "recipient_name",
-                        BluetoothMapUtils.stripInvalidChars(mRecipientName));
-            if(mRecipientAddressing != null)
-                xmlMsgElement.attribute(null, "recipient_addressing", mRecipientAddressing);
+            xmlMsgElement.attribute(null, "subject",
+                    stripped.substring(0, stripped.length() < 256 ? stripped.length() : 256));
+        }
+
+        if (mDateTime != 0) {
+            xmlMsgElement.attribute(null, "datetime", this.getDateTimeString());
+        }
+        if (mSenderName != null) {
+            xmlMsgElement.attribute(null, "sender_name",
+                    BluetoothMapUtils.stripInvalidChars(mSenderName));
+        }
+        if (mSenderAddressing != null) {
+            xmlMsgElement.attribute(null, "sender_addressing", mSenderAddressing);
+        }
+        if (mReplytoAddressing != null) {
+            xmlMsgElement.attribute(null, "replyto_addressing", mReplytoAddressing);
+        }
+        if (mRecipientName != null) {
+            xmlMsgElement.attribute(null, "recipient_name",
+                    BluetoothMapUtils.stripInvalidChars(mRecipientName));
+        }
+        if (mRecipientAddressing != null) {
+            xmlMsgElement.attribute(null, "recipient_addressing", mRecipientAddressing);
+        }
             /* Avoid NPE for possible "null" value of mType */
-            if(mMsgTypeAppParamSet == true && mType != null)
-                xmlMsgElement.attribute(null, "type", mType.name());
-            if(mSize != -1)
-                xmlMsgElement.attribute(null, "size", Integer.toString(mSize));
-            if(mText != null)
-                xmlMsgElement.attribute(null, "text", mText);
-            if(mReceptionStatus != null)
-                xmlMsgElement.attribute(null, "reception_status", mReceptionStatus);
-            if(mDeliveryStatus != null)
-                xmlMsgElement.attribute(null, "delivery_status", mDeliveryStatus);
-            if(mAttachmentSize != -1)
-                xmlMsgElement.attribute(null, "attachment_size",
-                        Integer.toString(mAttachmentSize));
-            if(mAttachmentMimeTypes != null)
-                xmlMsgElement.attribute(null, "attachment_mime_types", mAttachmentMimeTypes);
-            if(mPriority != null)
-                xmlMsgElement.attribute(null, "priority", mPriority);
-            if(mReportRead)
-                xmlMsgElement.attribute(null, "read", getRead());
-            if(mSent != null)
-                xmlMsgElement.attribute(null, "sent", mSent);
-            if(mProtect != null)
-                xmlMsgElement.attribute(null, "protected", mProtect);
-            if(mThreadId != null && includeThreadId == true)
-                xmlMsgElement.attribute(null, "conversation_id", mThreadId);
-            if(mThreadName != null && includeThreadId == true)
-                xmlMsgElement.attribute(null, "conversation_name", mThreadName);
-            if(mFolderType != null )
-                xmlMsgElement.attribute(null, "folder_type", mFolderType);
-            xmlMsgElement.endTag(null, "msg");
+        if (mMsgTypeAppParamSet && mType != null) {
+            xmlMsgElement.attribute(null, "type", mType.name());
+        }
+        if (mSize != -1) {
+            xmlMsgElement.attribute(null, "size", Integer.toString(mSize));
+        }
+        if (mText != null) {
+            xmlMsgElement.attribute(null, "text", mText);
+        }
+        if (mReceptionStatus != null) {
+            xmlMsgElement.attribute(null, "reception_status", mReceptionStatus);
+        }
+        if (mDeliveryStatus != null) {
+            xmlMsgElement.attribute(null, "delivery_status", mDeliveryStatus);
+        }
+        if (mAttachmentSize != -1) {
+            xmlMsgElement.attribute(null, "attachment_size", Integer.toString(mAttachmentSize));
+        }
+        if (mAttachmentMimeTypes != null) {
+            xmlMsgElement.attribute(null, "attachment_mime_types", mAttachmentMimeTypes);
+        }
+        if (mPriority != null) {
+            xmlMsgElement.attribute(null, "priority", mPriority);
+        }
+        if (mReportRead) {
+            xmlMsgElement.attribute(null, "read", getRead());
+        }
+        if (mSent != null) {
+            xmlMsgElement.attribute(null, "sent", mSent);
+        }
+        if (mProtect != null) {
+            xmlMsgElement.attribute(null, "protected", mProtect);
+        }
+        if (mThreadId != null && includeThreadId) {
+            xmlMsgElement.attribute(null, "conversation_id", mThreadId);
+        }
+        if (mThreadName != null && includeThreadId) {
+            xmlMsgElement.attribute(null, "conversation_name", mThreadName);
+        }
+        if (mFolderType != null) {
+            xmlMsgElement.attribute(null, "folder_type", mFolderType);
+        }
+        xmlMsgElement.endTag(null, "msg");
 
     }
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index 86f273c..afd1eb0 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -25,12 +25,12 @@
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.telephony.TelephonyManager;
 import android.text.format.DateUtils;
 import android.util.Log;
 
 import com.android.bluetooth.SignedLongLong;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
-import com.android.bluetooth.map.BluetoothMapMasInstance;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
 
 import java.io.IOException;
@@ -62,12 +62,24 @@
     private static final long THREAD_MAIL_KEY = 0x534c5349;
 
     // 128 bit UUID for MAP
-    private static final byte[] MAP_TARGET = new byte[] {
-             (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40,
-             (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
-             (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
-             (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
-             };
+    private static final byte[] MAP_TARGET = new byte[]{
+            (byte) 0xBB,
+            (byte) 0x58,
+            (byte) 0x2B,
+            (byte) 0x40,
+            (byte) 0x42,
+            (byte) 0x0C,
+            (byte) 0x11,
+            (byte) 0xDB,
+            (byte) 0xB0,
+            (byte) 0xDE,
+            (byte) 0x08,
+            (byte) 0x00,
+            (byte) 0x20,
+            (byte) 0x0C,
+            (byte) 0x9A,
+            (byte) 0x66
+    };
     public static final ParcelUuid MAP =
             ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB");
     public static final ParcelUuid MNS =
@@ -75,19 +87,17 @@
     public static final ParcelUuid MAS =
             ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
     /* Message types */
-    private static final String TYPE_GET_FOLDER_LISTING              = "x-obex/folder-listing";
-    private static final String TYPE_GET_MESSAGE_LISTING             = "x-bt/MAP-msg-listing";
-    private static final String TYPE_GET_CONVO_LISTING               = "x-bt/MAP-convo-listing";
-    private static final String TYPE_MESSAGE                         = "x-bt/message";
-    private static final String TYPE_SET_MESSAGE_STATUS              = "x-bt/messageStatus";
-    private static final String TYPE_SET_NOTIFICATION_REGISTRATION
-            = "x-bt/MAP-NotificationRegistration";
-    private static final String TYPE_MESSAGE_UPDATE                  = "x-bt/MAP-messageUpdate";
-    private static final String TYPE_GET_MAS_INSTANCE_INFORMATION
-            = "x-bt/MASInstanceInformation";
-    private static final String TYPE_SET_OWNER_STATUS                = "x-bt/participant";
-    private static final String TYPE_SET_NOTIFICATION_FILTER
-            = "x-bt/MAP-notification-filter";
+    private static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing";
+    private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
+    private static final String TYPE_GET_CONVO_LISTING = "x-bt/MAP-convo-listing";
+    private static final String TYPE_MESSAGE = "x-bt/message";
+    private static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus";
+    private static final String TYPE_SET_NOTIFICATION_REGISTRATION =
+            "x-bt/MAP-NotificationRegistration";
+    private static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate";
+    private static final String TYPE_GET_MAS_INSTANCE_INFORMATION = "x-bt/MASInstanceInformation";
+    private static final String TYPE_SET_OWNER_STATUS = "x-bt/participant";
+    private static final String TYPE_SET_NOTIFICATION_FILTER = "x-bt/MAP-notification-filter";
 
     private static final int MAS_INSTANCE_INFORMATION_LENGTH = 200;
 
@@ -113,12 +123,9 @@
     private ContentResolver mResolver;
     private ContentProviderClient mProviderClient = null;
 
-    public BluetoothMapObexServer(Handler callback,
-                                  Context context,
-                                  BluetoothMapContentObserver observer,
-                                  BluetoothMapMasInstance mas,
-                                  BluetoothMapAccountItem account,
-                                  boolean enableSmsMms) throws RemoteException {
+    public BluetoothMapObexServer(Handler callback, Context context,
+            BluetoothMapContentObserver observer, BluetoothMapMasInstance mas,
+            BluetoothMapAccountItem account, boolean enableSmsMms) throws RemoteException {
         super();
         mCallback = callback;
         mContext = context;
@@ -129,17 +136,23 @@
         mMasInstance = mas;
         mRemoteFeatureMask = mMasInstance.getRemoteFeatureMask();
 
-        if(account != null && account.getProviderAuthority() != null) {
+        if (account != null && account.getProviderAuthority() != null) {
             mAccountId = account.getAccountId();
             mAuthority = account.getProviderAuthority();
             mResolver = mContext.getContentResolver();
-            if (D) Log.d(TAG, "BluetoothMapObexServer(): accountId=" + mAccountId);
+            if (D) {
+                Log.d(TAG, "BluetoothMapObexServer(): accountId=" + mAccountId);
+            }
             mBaseUriString = account.mBase_uri + "/";
-            if (D) Log.d(TAG, "BluetoothMapObexServer(): baseUri=" + mBaseUriString);
+            if (D) {
+                Log.d(TAG, "BluetoothMapObexServer(): baseUri=" + mBaseUriString);
+            }
             if (account.getType() == TYPE.EMAIL) {
-                mEmailFolderUri = BluetoothMapContract.buildFolderUri(mAuthority,
-                                                                 Long.toString(mAccountId));
-                if (D) Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri);
+                mEmailFolderUri =
+                        BluetoothMapContract.buildFolderUri(mAuthority, Long.toString(mAccountId));
+                if (D) {
+                    Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri);
+                }
             }
             mProviderClient = acquireUnstableContentProviderOrThrow();
         }
@@ -157,7 +170,7 @@
      */
     private ContentProviderClient acquireUnstableContentProviderOrThrow() throws RemoteException {
         ContentProviderClient providerClient =
-                            mResolver.acquireUnstableContentProviderClient(mAuthority);
+                mResolver.acquireUnstableContentProviderClient(mAuthority);
         if (providerClient == null) {
             throw new RemoteException("Failed to acquire provider for " + mAuthority);
         }
@@ -169,15 +182,18 @@
      * Build the default minimal folder structure, as defined in the MAP specification.
      */
     private void buildFolderStructure() throws RemoteException {
-        mCurrentFolder = new BluetoothMapFolderElement("root", null);//This will be the root element
+        //This will be the root element
+        mCurrentFolder = new BluetoothMapFolderElement("root", null);
         mCurrentFolder.setHasSmsMmsContent(mEnableSmsMms);
         boolean hasIM = false;
         boolean hasEmail = false;
         if (mAccount != null) {
-           if (mAccount.getType() == TYPE.IM)
-               hasIM = true;
-           if( mAccount.getType() == TYPE.EMAIL)
-               hasEmail = true;
+            if (mAccount.getType() == TYPE.IM) {
+                hasIM = true;
+            }
+            if (mAccount.getType() == TYPE.EMAIL) {
+                hasEmail = true;
+            }
         }
         mCurrentFolder.setHasImContent(hasIM);
         mCurrentFolder.setHasEmailContent(hasEmail);
@@ -199,7 +215,9 @@
             addSmsMmsFolders(tmpFolder);
         }
         if (hasEmail) {
-            if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
+            if (D) {
+                Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
+            }
             addEmailFolders(tmpFolder);
         }
         if (hasIM) {
@@ -263,26 +281,30 @@
         // Select all parent folders
         BluetoothMapFolderElement newFolder;
 
-        String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID +
-                        " = " + parentFolder.getFolderId();
-        Cursor c = mProviderClient.query(mEmailFolderUri,
-                        BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null);
+        String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID + " = "
+                + parentFolder.getFolderId();
+        Cursor c = mProviderClient.query(mEmailFolderUri, BluetoothMapContract.BT_FOLDER_PROJECTION,
+                where, null, null);
         try {
             if (c != null) {
                 c.moveToPosition(-1);
                 while (c.moveToNext()) {
-                    String name = c.getString(c.getColumnIndex(
-                            BluetoothMapContract.FolderColumns.NAME));
+                    String name =
+                            c.getString(c.getColumnIndex(BluetoothMapContract.FolderColumns.NAME));
                     long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
                     newFolder = parentFolder.addEmailFolder(name, id);
                     addEmailFolders(newFolder); // Use recursion to add any sub folders
                 }
 
             } else {
-                if (D) Log.d(TAG, "addEmailFolders(): no elements found");
+                if (D) {
+                    Log.d(TAG, "addEmailFolders(): no elements found");
+                }
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
     }
 
@@ -297,26 +319,35 @@
     }
 
     public void setRemoteFeatureMask(int mRemoteFeatureMask) {
-        if(D) Log.d(TAG, "setRemoteFeatureMask() " + Integer.toHexString(mRemoteFeatureMask));
+        if (D) {
+            Log.d(TAG, "setRemoteFeatureMask() " + Integer.toHexString(mRemoteFeatureMask));
+        }
         this.mRemoteFeatureMask = mRemoteFeatureMask;
         this.mOutContent.setRemoteFeatureMask(mRemoteFeatureMask);
     }
 
     @Override
     public int onConnect(final HeaderSet request, HeaderSet reply) {
-        if (D) Log.d(TAG, "onConnect():");
-        if (V) logHeader(request);
+        if (D) {
+            Log.d(TAG, "onConnect():");
+        }
+        if (V) {
+            logHeader(request);
+        }
         mThreadIdSupport = false; // Always assume not supported at new connect.
-        mMessageVersion = BluetoothMapUtils.MAP_V10_STR;//always assume version 1.0 to start with
+        //always assume version 1.0 to start with
+        mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
         notifyUpdateWakeLock();
         Long threadedMailKey = null;
         try {
-            byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
-            threadedMailKey = (Long)request.getHeader(THREADED_MAIL_HEADER_ID);
+            byte[] uuid = (byte[]) request.getHeader(HeaderSet.TARGET);
+            threadedMailKey = (Long) request.getHeader(THREADED_MAIL_HEADER_ID);
             if (uuid == null) {
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
             }
-            if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
+            if (D) {
+                Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
+            }
 
             if (uuid.length != UUID_LENGTH) {
                 Log.w(TAG, "Wrong UUID length");
@@ -330,18 +361,19 @@
             }
             reply.setHeader(HeaderSet.WHO, uuid);
         } catch (IOException e) {
-            Log.e(TAG,"Exception during onConnect:", e);
+            Log.e(TAG, "Exception during onConnect:", e);
             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
         }
 
         try {
-            byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
+            byte[] remote = (byte[]) request.getHeader(HeaderSet.WHO);
             if (remote != null) {
-                if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
+                if (D) {
+                    Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
+                }
                 reply.setHeader(HeaderSet.TARGET, remote);
             }
-            if(threadedMailKey != null && threadedMailKey.longValue() == THREAD_MAIL_KEY)
-            {
+            if (threadedMailKey != null && threadedMailKey.longValue() == THREAD_MAIL_KEY) {
                 /* If the client provides the correct key we enable threaded e-mail support
                  * and reply to the client that we support the requested feature.
                  * This is currently an Android only feature. */
@@ -349,25 +381,26 @@
                 reply.setHeader(THREADED_MAIL_HEADER_ID, THREAD_MAIL_KEY);
             }
         } catch (IOException e) {
-            Log.e(TAG,"Exception during onConnect:", e);
+            Log.e(TAG, "Exception during onConnect:", e);
             mThreadIdSupport = false;
             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
         }
 
-        if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
+        if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
                 == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
             mThreadIdSupport = true;
         }
 
-        if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT)
-                == BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT){
+        if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT)
+                == BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT) {
             mMessageVersion = BluetoothMapUtils.MAP_V11_STR;
         }
 
-        if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
-                "MSG_SESSION_ESTABLISHED msg.");
+        if (V) {
+            Log.v(TAG, "onConnect(): uuid is ok, will send out " + "MSG_SESSION_ESTABLISHED msg.");
+        }
 
-        if(mCallback != null) {
+        if (mCallback != null) {
             Message msg = Message.obtain(mCallback);
             msg.what = BluetoothMapService.MSG_SESSION_ESTABLISHED;
             msg.sendToTarget();
@@ -378,21 +411,29 @@
 
     @Override
     public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
-        if (D) Log.d(TAG, "onDisconnect(): enter");
-        if (V) logHeader(req);
+        if (D) {
+            Log.d(TAG, "onDisconnect(): enter");
+        }
+        if (V) {
+            logHeader(req);
+        }
         notifyUpdateWakeLock();
         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
         if (mCallback != null) {
             Message msg = Message.obtain(mCallback);
             msg.what = BluetoothMapService.MSG_SESSION_DISCONNECTED;
             msg.sendToTarget();
-            if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
+            if (V) {
+                Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
+            }
         }
     }
 
     @Override
     public int onAbort(HeaderSet request, HeaderSet reply) {
-        if (D) Log.d(TAG, "onAbort(): enter.");
+        if (D) {
+            Log.d(TAG, "onAbort(): enter.");
+        }
         notifyUpdateWakeLock();
         mIsAborted = true;
         return ResponseCodes.OBEX_HTTP_OK;
@@ -405,7 +446,9 @@
 
     @Override
     public int onPut(final Operation op) {
-        if (D) Log.d(TAG, "onPut(): enter");
+        if (D) {
+            Log.d(TAG, "onPut(): enter");
+        }
         mIsAborted = false;
         notifyUpdateWakeLock();
         HeaderSet request = null;
@@ -415,27 +458,32 @@
 
         try {
             request = op.getReceivedHeader();
-            if (V) logHeader(request);
-            type = (String)request.getHeader(HeaderSet.TYPE);
-            name = (String)request.getHeader(HeaderSet.NAME);
-            appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
-            if(appParamRaw != null)
+            if (V) {
+                logHeader(request);
+            }
+            type = (String) request.getHeader(HeaderSet.TYPE);
+            name = (String) request.getHeader(HeaderSet.NAME);
+            appParamRaw = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER);
+            if (appParamRaw != null) {
                 appParams = new BluetoothMapAppParams(appParamRaw);
-            if(D) Log.d(TAG,"type = " + type + ", name = " + name);
+            }
+            if (D) {
+                Log.d(TAG, "type = " + type + ", name = " + name);
+            }
             if (type.equals(TYPE_MESSAGE_UPDATE)) {
-                if(V) {
-                    Log.d(TAG,"TYPE_MESSAGE_UPDATE:");
+                if (V) {
+                    Log.d(TAG, "TYPE_MESSAGE_UPDATE:");
                 }
                 return updateInbox();
             } else if (type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) {
-                if(V) {
-                    Log.d(TAG,"TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: "
+                if (V) {
+                    Log.d(TAG, "TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: "
                             + appParams.getNotificationStatus());
                 }
                 return mObserver.setNotificationRegistration(appParams.getNotificationStatus());
             } else if (type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
-                if(V) {
-                    Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
+                if (V) {
+                    Log.d(TAG, "TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
                             + appParams.getNotificationFilter());
                 }
                 if (!isUserUnlocked()) {
@@ -445,11 +493,11 @@
                 mObserver.setNotificationFilter(appParams.getNotificationFilter());
                 return ResponseCodes.OBEX_HTTP_OK;
             } else if (type.equals(TYPE_SET_MESSAGE_STATUS)) {
-                if(V) {
-                    Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: " +
-                              "StatusIndicator: " + appParams.getStatusIndicator()
-                            + ", StatusValue: " + appParams.getStatusValue()
-                            + ", ExtentedData: " + "" ); // TODO:   appParams.getExtendedImData());
+                if (V) {
+                    Log.d(TAG, "TYPE_SET_MESSAGE_STATUS: " + "StatusIndicator: "
+                            + appParams.getStatusIndicator() + ", StatusValue: "
+                            + appParams.getStatusValue()
+                            + ", ExtentedData: "); // TODO:   appParams.getExtendedImData());
                 }
                 if (!isUserUnlocked()) {
                     Log.e(TAG, "Storage locked, " + type + " failed");
@@ -457,10 +505,11 @@
                 }
                 return setMessageStatus(name, appParams);
             } else if (type.equals(TYPE_MESSAGE)) {
-                if(V) {
-                    Log.d(TAG,"TYPE_MESSAGE: Transparet: " + appParams.getTransparent()
-                            + ", retry: " + appParams.getRetry()
-                            + ", charset: " + appParams.getCharset());
+                if (V) {
+                    Log.d(TAG,
+                            "TYPE_MESSAGE: Transparet: " + appParams.getTransparent() + ", retry: "
+                                    + appParams.getRetry() + ", charset: "
+                                    + appParams.getCharset());
                 }
                 if (!isUserUnlocked()) {
                     Log.e(TAG, "Storage locked, " + type + " failed");
@@ -468,33 +517,33 @@
                 }
                 return pushMessage(op, name, appParams, mMessageVersion);
             } else if (type.equals(TYPE_SET_OWNER_STATUS)) {
-                if(V) {
-                    Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
-                          " PresenceAvailability " + appParams.getPresenceAvailability() +
-                          ", PresenceStatus: " + appParams.getPresenceStatus() +
-                          ", LastActivity: " + appParams.getLastActivityString() +
-                          ", ChatStatus: " + appParams.getChatState() +
-                          ", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
+                if (V) {
+                    Log.d(TAG, "TYPE_SET_OWNER_STATUS:" + " PresenceAvailability "
+                            + appParams.getPresenceAvailability() + ", PresenceStatus: " + appParams
+                            .getPresenceStatus() + ", LastActivity: "
+                            + appParams.getLastActivityString() + ", ChatStatus: "
+                            + appParams.getChatState() + ", ChatStatusConvoId: "
+                            + appParams.getChatStateConvoIdString());
                 }
                 return setOwnerStatus(name, appParams);
             }
 
-        } catch (RemoteException e){
+        } catch (RemoteException e) {
             //reload the providerClient and return error
             try {
                 mProviderClient = acquireUnstableContentProviderOrThrow();
-            }catch (RemoteException e2){
+            } catch (RemoteException e2) {
                 //should not happen
             }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
-        }catch (Exception e) {
+        } catch (Exception e) {
 
-            if(D) {
-                Log.e(TAG, "Exception occured while handling request",e);
+            if (D) {
+                Log.e(TAG, "Exception occured while handling request", e);
             } else {
                 Log.e(TAG, "Exception occured while handling request");
             }
-            if(mIsAborted) {
+            if (mIsAborted) {
                 return ResponseCodes.OBEX_HTTP_OK;
             } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
@@ -503,49 +552,66 @@
         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
     }
 
-    private int updateInbox() throws RemoteException{
+    private int updateInbox() throws RemoteException {
         if (mAccount != null) {
-            BluetoothMapFolderElement inboxFolder = mCurrentFolder.getFolderByName(
-                    BluetoothMapContract.FOLDER_NAME_INBOX);
+            BluetoothMapFolderElement inboxFolder =
+                    mCurrentFolder.getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX);
             if (inboxFolder != null) {
                 long accountId = mAccountId;
-                if (D) Log.d(TAG,"updateInbox inbox=" + inboxFolder.getName() + "id="
-                        + inboxFolder.getFolderId());
+                if (D) {
+                    Log.d(TAG, "updateInbox inbox=" + inboxFolder.getName() + "id="
+                            + inboxFolder.getFolderId());
+                }
 
                 final Bundle extras = new Bundle(2);
                 if (accountId != -1) {
-                    if (D) Log.d(TAG,"updateInbox accountId=" + accountId);
+                    if (D) {
+                        Log.d(TAG, "updateInbox accountId=" + accountId);
+                    }
                     extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID,
                             inboxFolder.getFolderId());
                     extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId);
                 } else {
                     // Only error code allowed on an UpdateInbox is OBEX_HTTP_NOT_IMPLEMENTED,
                     // i.e. if e.g. update not allowed on the mailbox
-                    if (D) Log.d(TAG,"updateInbox accountId=0 -> OBEX_HTTP_NOT_IMPLEMENTED");
+                    if (D) {
+                        Log.d(TAG, "updateInbox accountId=0 -> OBEX_HTTP_NOT_IMPLEMENTED");
+                    }
                     return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
                 }
 
                 Uri emailUri = Uri.parse(mBaseUriString);
-                if (D) Log.d(TAG,"updateInbox in: " + emailUri.toString());
+                if (D) {
+                    Log.d(TAG, "updateInbox in: " + emailUri.toString());
+                }
                 try {
-                    if (D) Log.d(TAG,"updateInbox call()...");
-                    Bundle myBundle = mProviderClient.call(
-                            BluetoothMapContract.METHOD_UPDATE_FOLDER, null, extras);
-                    if (myBundle != null)
+                    if (D) {
+                        Log.d(TAG, "updateInbox call()...");
+                    }
+                    Bundle myBundle =
+                            mProviderClient.call(BluetoothMapContract.METHOD_UPDATE_FOLDER, null,
+                                    extras);
+                    if (myBundle != null) {
                         return ResponseCodes.OBEX_HTTP_OK;
-                    else {
-                        if (D) Log.d(TAG,"updateInbox call failed");
+                    } else {
+                        if (D) {
+                            Log.d(TAG, "updateInbox call failed");
+                        }
                         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
                     }
-                } catch (RemoteException e){
+                } catch (RemoteException e) {
                     mProviderClient = acquireUnstableContentProviderOrThrow();
                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
-                }catch (NullPointerException e) {
-                    if(D) Log.e(TAG, "UpdateInbox - if uri or method is null", e);
+                } catch (NullPointerException e) {
+                    if (D) {
+                        Log.e(TAG, "UpdateInbox - if uri or method is null", e);
+                    }
                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
 
                 } catch (IllegalArgumentException e) {
-                    if(D) Log.e(TAG, "UpdateInbox - if uri is not known", e);
+                    if (D) {
+                        Log.e(TAG, "UpdateInbox - if uri is not known", e);
+                    }
                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
                 }
             }
@@ -554,43 +620,51 @@
         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
     }
 
-     private BluetoothMapFolderElement getFolderElementFromName(String folderName) {
+    private BluetoothMapFolderElement getFolderElementFromName(String folderName) {
         BluetoothMapFolderElement folderElement = null;
 
-        if(folderName == null || folderName.trim().isEmpty() ) {
+        if (folderName == null || folderName.trim().isEmpty()) {
             folderElement = mCurrentFolder;
-            if(D) Log.d(TAG, "no folder name supplied, setting folder to current: "
-                             + folderElement.getName());
+            if (D) {
+                Log.d(TAG, "no folder name supplied, setting folder to current: "
+                        + folderElement.getName());
+            }
         } else {
             folderElement = mCurrentFolder.getSubFolder(folderName);
             if (folderElement != null) {
-                if(D) Log.d(TAG, "Folder name: " + folderName +
-                                 " resulted in this element: " + folderElement.getName());
+                if (D) {
+                    Log.d(TAG, "Folder name: " + folderName + " resulted in this element: "
+                            + folderElement.getName());
+                }
             }
         }
         return folderElement;
     }
 
-    private int pushMessage(final Operation op, String folderName,
-            BluetoothMapAppParams appParams, String messageVersion) {
-        if(appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
-            if(D) Log.d(TAG, "pushMessage: Missing charset - unable to decode message content. " +
-                    "appParams.getCharset() = " + appParams.getCharset());
+    private int pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams,
+            String messageVersion) {
+        if (appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+            if (D) {
+                Log.d(TAG, "pushMessage: Missing charset - unable to decode message content. "
+                        + "appParams.getCharset() = " + appParams.getCharset());
+            }
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
         }
         InputStream bMsgStream = null;
         try {
             BluetoothMapFolderElement folderElement = getFolderElementFromName(folderName);
-            if(folderElement == null) {
-                Log.w(TAG,"pushMessage: folderElement == null - sending OBEX_HTTP_PRECON_FAILED");
+            if (folderElement == null) {
+                Log.w(TAG, "pushMessage: folderElement == null - sending OBEX_HTTP_PRECON_FAILED");
                 return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
             } else {
                 folderName = folderElement.getName();
             }
-            if (!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) &&
-                    !folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
-                if(D) Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " +
-                        "folderName=" + folderName);
+            if (!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) && !folderName
+                    .equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
+                if (D) {
+                    Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " + "folderName="
+                            + folderName);
+                }
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
             }
 
@@ -603,32 +677,58 @@
             // Decode the messageBody
             message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset());
             message.setVersionString(messageVersion);
+            if (D) {
+                Log.d(TAG, "pushMessage: charset" + appParams.getCharset() + "folderId: "
+                                + folderElement.getFolderId() + "Name: " + folderName + "TYPE: "
+                                + message.getType());
+            }
+            if (message.getType().equals(TYPE.SMS_GSM) || message.getType().equals(TYPE.SMS_CDMA)) {
+                // Convert messages to the default network type.
+                TelephonyManager tm =
+                        (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+                if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+                    message.setType(TYPE.SMS_GSM);
+                } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+                    message.setType(TYPE.SMS_CDMA);
+                }
+                if (D) {
+                    Log.d(TAG, "Updated message type: " + message.getType());
+                }
+            }
             // Send message
             if (mObserver == null || message == null) {
                 // Should not happen except at shutdown.
-                if(D) Log.w(TAG, "mObserver or parsed message not available" );
+                if (D) {
+                    Log.w(TAG, "mObserver or parsed message not available");
+                }
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
             }
 
-            if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getFolderId() == -1))
-                    || ((message.getType().equals(TYPE.SMS_GSM) ||
-                         message.getType().equals(TYPE.SMS_CDMA) ||
-                         message.getType().equals(TYPE.MMS))
-                         && !folderElement.hasSmsMmsContent()) ) {
-                if(D) Log.w(TAG, "Wrong message type recieved" );
+            if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getFolderId() == -1)) || (
+                    (message.getType().equals(TYPE.SMS_GSM) || message.getType()
+                            .equals(TYPE.SMS_CDMA) || message.getType().equals(TYPE.MMS))
+                            && !folderElement.hasSmsMmsContent())) {
+                if (D) {
+                    Log.w(TAG, "Wrong message type recieved");
+                }
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
             }
 
             long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseUriString);
-            if (D) Log.d(TAG, "pushMessage handle: " + handle);
+            if (D) {
+                Log.d(TAG, "pushMessage handle: " + handle);
+            }
             if (handle < 0) {
-                if(D) Log.w(TAG, "Message  handle not created" );
+                if (D) {
+                    Log.w(TAG, "Message  handle not created");
+                }
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
             }
             HeaderSet replyHeaders = new HeaderSet();
             String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType());
-            if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): "
-                               + message.getType());
+            if (D) {
+                Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType());
+            }
             replyHeaders.setHeader(HeaderSet.NAME, handleStr);
             op.sendHeaders(replyHeaders);
 
@@ -636,30 +736,41 @@
             //reload the providerClient and return error
             try {
                 mProviderClient = acquireUnstableContentProviderOrThrow();
-            }catch (RemoteException e2){
+            } catch (RemoteException e2) {
                 //should not happen
-                if(D) Log.w(TAG, "acquireUnstableContentProviderOrThrow FAILED");
+                if (D) {
+                    Log.w(TAG, "acquireUnstableContentProviderOrThrow FAILED");
+                }
             }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
         } catch (IllegalArgumentException e) {
-            if (D) Log.e(TAG, "Wrongly formatted bMessage received", e);
+            if (D) {
+                Log.e(TAG, "Wrongly formatted bMessage received", e);
+            }
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
         } catch (IOException e) {
-            if (D) Log.e(TAG, "Exception occured: ", e);
-            if(mIsAborted == true) {
-                if(D) Log.d(TAG, "PushMessage Operation Aborted");
+            if (D) {
+                Log.e(TAG, "Exception occured: ", e);
+            }
+            if (mIsAborted) {
+                if (D) {
+                    Log.d(TAG, "PushMessage Operation Aborted");
+                }
                 return ResponseCodes.OBEX_HTTP_OK;
             } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } catch (Exception e) {
-            if (D) Log.e(TAG, "Exception:", e);
+            if (D) {
+                Log.e(TAG, "Exception:", e);
+            }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
         } finally {
-            if(bMsgStream != null) {
+            if (bMsgStream != null) {
                 try {
                     bMsgStream.close();
-                } catch (IOException e) {}
+                } catch (IOException e) {
+                }
             }
         }
         return ResponseCodes.OBEX_HTTP_OK;
@@ -675,20 +786,24 @@
 
         if (msgHandle == null) {
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
-        } else if ((indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
-                   value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) &&
-                   extendedData == null) {
+        } else if ((indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER
+                || value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+                && extendedData == null) {
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
         }
         if (mObserver == null) {
-            if(D) Log.e(TAG, "Error: no mObserver!");
+            if (D) {
+                Log.e(TAG, "Error: no mObserver!");
+            }
             return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
         }
 
         try {
             handle = BluetoothMapUtils.getCpHandle(msgHandle);
             msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle);
-            if(D) Log.d(TAG,"setMessageStatus. Handle:" + handle+", MsgType: "+ msgType);
+            if (D) {
+                Log.d(TAG, "setMessageStatus. Handle:" + handle + ", MsgType: " + msgType);
+            }
         } catch (NumberFormatException e) {
             Log.w(TAG, "Wrongly formatted message handle: " + msgHandle);
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
@@ -697,32 +812,34 @@
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
         }
 
-        if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
-            if (!mObserver.setMessageStatusDeleted(handle, msgType, mCurrentFolder,
-                    mBaseUriString, value)) {
-                if(D) Log.w(TAG,"setMessageStatusDeleted failed");
+        if (indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
+            if (!mObserver.setMessageStatusDeleted(handle, msgType, mCurrentFolder, mBaseUriString,
+                    value)) {
+                if (D) {
+                    Log.w(TAG, "setMessageStatusDeleted failed");
+                }
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
             }
-        } else if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_READ) {
+        } else if (indicator == BluetoothMapAppParams.STATUS_INDICATOR_READ) {
             try {
                 if (!mObserver.setMessageStatusRead(handle, msgType, mBaseUriString, value)) {
-                    if(D) Log.w(TAG,"not able to update the message");
+                    if (D) {
+                        Log.w(TAG, "not able to update the message");
+                    }
                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
                 }
             } catch (RemoteException e) {
-                if(D) Log.w(TAG,"Error in setMessageStatusRead()", e);
+                if (D) {
+                    Log.w(TAG, "Error in setMessageStatusRead()", e);
+                }
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
             }
         }
-        if (extendedData != null) {
-
-        }
-
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
     private int setOwnerStatus(String msgHandle, BluetoothMapAppParams appParams)
-            throws RemoteException{
+            throws RemoteException {
         // This does only work for IM
         if (mAccount != null && mAccount.getType() == BluetoothMapUtils.TYPE.IM) {
             final Bundle extras = new Bundle(5);
@@ -733,49 +850,60 @@
             int chatState = appParams.getChatState();
             String chatStatusConvoId = appParams.getChatStateConvoIdString();
 
-            if(presenceState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
-               presenceStatus == null &&
-               lastActivity == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
-               chatState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
-               chatStatusConvoId == null) {
+            if (presenceState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER
+                    && presenceStatus == null
+                    && lastActivity == BluetoothMapAppParams.INVALID_VALUE_PARAMETER
+                    && chatState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER
+                    && chatStatusConvoId == null) {
                 return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
             }
 
-            if(presenceState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+            if (presenceState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 extras.putInt(BluetoothMapContract.EXTRA_PRESENCE_STATE, presenceState);
             }
-            if (presenceStatus != null){
+            if (presenceStatus != null) {
                 extras.putString(BluetoothMapContract.EXTRA_PRESENCE_STATUS, presenceStatus);
             }
-            if (lastActivity != BluetoothMapAppParams.INVALID_VALUE_PARAMETER){
+            if (lastActivity != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 extras.putLong(BluetoothMapContract.EXTRA_LAST_ACTIVE, lastActivity);
             }
-            if (chatState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
-                chatStatusConvoId != null){
+            if (chatState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER
+                    && chatStatusConvoId != null) {
                 extras.putInt(BluetoothMapContract.EXTRA_CHAT_STATE, chatState);
                 extras.putString(BluetoothMapContract.EXTRA_CONVERSATION_ID, chatStatusConvoId);
             }
 
             Uri uri = Uri.parse(mBaseUriString);
-            if (D) Log.d(TAG,"setOwnerStatus in: " + uri.toString());
+            if (D) {
+                Log.d(TAG, "setOwnerStatus in: " + uri.toString());
+            }
             try {
-                if (D) Log.d(TAG,"setOwnerStatus call()...");
-                Bundle myBundle = mProviderClient.call(
-                        BluetoothMapContract.METHOD_SET_OWNER_STATUS, null, extras);
-                if (myBundle != null)
+                if (D) {
+                    Log.d(TAG, "setOwnerStatus call()...");
+                }
+                Bundle myBundle =
+                        mProviderClient.call(BluetoothMapContract.METHOD_SET_OWNER_STATUS, null,
+                                extras);
+                if (myBundle != null) {
                     return ResponseCodes.OBEX_HTTP_OK;
-                else {
-                    if (D) Log.d(TAG,"setOwnerStatus call failed");
+                } else {
+                    if (D) {
+                        Log.d(TAG, "setOwnerStatus call failed");
+                    }
                     return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
                 }
-            } catch (RemoteException e){
+            } catch (RemoteException e) {
                 mProviderClient = acquireUnstableContentProviderOrThrow();
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
             } catch (NullPointerException e) {
-                if(D) Log.e(TAG, "setOwnerStatus - if uri or method is null", e);
+                if (D) {
+                    Log.e(TAG, "setOwnerStatus - if uri or method is null", e);
+                }
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
             } catch (IllegalArgumentException e) {
-                if(D) Log.e(TAG, "setOwnerStatus - if uri is not known", e);
+                if (D) {
+                    Log.e(TAG, "setOwnerStatus - if uri is not known", e);
+                }
                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
             }
         }
@@ -789,40 +917,47 @@
         BluetoothMapFolderElement folder;
         notifyUpdateWakeLock();
         try {
-            folderName = (String)request.getHeader(HeaderSet.NAME);
+            folderName = (String) request.getHeader(HeaderSet.NAME);
         } catch (Exception e) {
-            if(D) {
-                Log.e(TAG, "request headers error" , e);
+            if (D) {
+                Log.e(TAG, "request headers error", e);
             } else {
                 Log.e(TAG, "request headers error");
             }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
         }
 
-        if (V) logHeader(request);
-        if (D) Log.d(TAG, "onSetPath name is " + folderName +
-                          " backup: " + backup +
-                          " create: " + create);
+        if (V) {
+            logHeader(request);
+        }
+        if (D) {
+            Log.d(TAG, "onSetPath name is " + folderName + " backup: " + backup + " create: "
+                    + create);
+        }
 
-        if(backup == true){
-            if(mCurrentFolder.getParent() != null)
+        if (backup) {
+            if (mCurrentFolder.getParent() != null) {
                 mCurrentFolder = mCurrentFolder.getParent();
-            else
+            } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
         }
 
         if (folderName == null || folderName.trim().isEmpty()) {
-            if(backup == false)
+            if (!backup) {
                 mCurrentFolder = mCurrentFolder.getRoot();
-        }
-        else {
+            }
+        } else {
             folder = mCurrentFolder.getSubFolder(folderName);
-            if(folder != null)
+            if (folder != null) {
                 mCurrentFolder = folder;
-            else
+            } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
         }
-        if (V) Log.d(TAG, "Current Folder: " + mCurrentFolder.getName());
+        if (V) {
+            Log.d(TAG, "Current Folder: " + mCurrentFolder.getName());
+        }
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
@@ -833,10 +968,12 @@
             msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE;
             msg.arg1 = mMasId;
             msg.sendToTarget();
-            if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
+            if (D) {
+                Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
+            }
 
         }
-        if(mProviderClient != null){
+        if (mProviderClient != null) {
             mProviderClient.release();
             mProviderClient = null;
         }
@@ -854,89 +991,97 @@
         BluetoothMapAppParams appParams = null;
         try {
             request = op.getReceivedHeader();
-            type = (String)request.getHeader(HeaderSet.TYPE);
+            type = (String) request.getHeader(HeaderSet.TYPE);
 
-            appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
-            if(appParamRaw != null)
+            appParamRaw = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER);
+            if (appParamRaw != null) {
                 appParams = new BluetoothMapAppParams(appParamRaw);
+            }
 
-            if (V) logHeader(request);
-            if (D) Log.d(TAG, "OnGet type is " + type );
+            if (V) {
+                logHeader(request);
+            }
+            if (D) {
+                Log.d(TAG, "OnGet type is " + type);
+            }
 
             if (type == null) {
-                if (V) Log.d(TAG, "type is null?" + type);
+                if (V) {
+                    Log.d(TAG, "type is null?" + type);
+                }
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
 
             if (type.equals(TYPE_GET_FOLDER_LISTING)) {
                 if (V && appParams != null) {
-                    Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = "
-                            + appParams.getMaxListCount()
-                            + ", ListStartOffset = " + appParams.getStartOffset());
+                    Log.d(TAG,
+                            "TYPE_GET_FOLDER_LISTING: MaxListCount = " + appParams.getMaxListCount()
+                                    + ", ListStartOffset = " + appParams.getStartOffset());
                 }
                 // Block until all packets have been send.
                 return sendFolderListingRsp(op, appParams);
-            } else if (type.equals(TYPE_GET_MESSAGE_LISTING)){
-                name = (String)request.getHeader(HeaderSet.NAME);
+            } else if (type.equals(TYPE_GET_MESSAGE_LISTING)) {
+                name = (String) request.getHeader(HeaderSet.NAME);
                 if (V && appParams != null) {
-                    Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: folder name is: " + name +
-                            ", MaxListCount = " + appParams.getMaxListCount() +
-                            ", ListStartOffset = " + appParams.getStartOffset());
-                    Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() +
-                            ", ParameterMask = " + appParams.getParameterMask());
-                    Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() );
-                    Log.d(TAG,"FilterPeriodBegin = " + appParams.getFilterPeriodBeginString() +
-                            ", FilterPeriodEnd = " + appParams.getFilterPeriodEndString() +
-                            ", FilterReadStatus = " + appParams.getFilterReadStatus());
-                    Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient() +
-                            ", FilterOriginator = " + appParams.getFilterOriginator());
-                    Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority());
+                    Log.d(TAG, "TYPE_GET_MESSAGE_LISTING: folder name is: " + name
+                            + ", MaxListCount = " + appParams.getMaxListCount()
+                            + ", ListStartOffset = " + appParams.getStartOffset());
+                    Log.d(TAG,
+                            "SubjectLength = " + appParams.getSubjectLength() + ", ParameterMask = "
+                                    + appParams.getParameterMask());
+                    Log.d(TAG, "FilterMessageType = " + appParams.getFilterMessageType());
+                    Log.d(TAG, "FilterPeriodBegin = " + appParams.getFilterPeriodBeginString()
+                            + ", FilterPeriodEnd = " + appParams.getFilterPeriodEndString()
+                            + ", FilterReadStatus = " + appParams.getFilterReadStatus());
+                    Log.d(TAG, "FilterRecipient = " + appParams.getFilterRecipient()
+                            + ", FilterOriginator = " + appParams.getFilterOriginator());
+                    Log.d(TAG, "FilterPriority = " + appParams.getFilterPriority());
                     long tmpLong = appParams.getFilterMsgHandle();
-                    Log.d(TAG,"FilterMsgHandle = " + (
-                            (tmpLong == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? "" :
-                                Long.toHexString(tmpLong)));
+                    Log.d(TAG, "FilterMsgHandle = " + (
+                            (tmpLong == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? ""
+                                    : Long.toHexString(tmpLong)));
                     SignedLongLong tmpLongLong = appParams.getFilterConvoId();
-                    Log.d(TAG,"FilterConvoId = " + ((tmpLongLong == null) ? "" :
-                        Long.toHexString(tmpLongLong.getLeastSignificantBits()) ) );
-                }
-                if (!isUserUnlocked()) {
-                    Log.e(TAG, "Storage locked, " +  type + " failed");
-                    return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
-                }
-                // Block until all packets have been send.
-                return sendMessageListingRsp(op, appParams, name);
-
-            } else if (type.equals(TYPE_GET_CONVO_LISTING)){
-                name = (String)request.getHeader(HeaderSet.NAME);
-                if (V && appParams != null) {
-                    Log.d(TAG,"TYPE_GET_CONVO_LISTING: name is" + name +
-                            ", MaxListCount = " + appParams.getMaxListCount() +
-                            ", ListStartOffset = " + appParams.getStartOffset());
-                    Log.d(TAG,"FilterLastActivityBegin = "+appParams.getFilterLastActivityBegin());
-                    Log.d(TAG,"FilterLastActivityEnd = " + appParams.getFilterLastActivityEnd());
-                    Log.d(TAG,"FilterReadStatus = " + appParams.getFilterReadStatus());
-                    Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient());
+                    Log.d(TAG, "FilterConvoId = " + ((tmpLongLong == null) ? ""
+                            : Long.toHexString(tmpLongLong.getLeastSignificantBits())));
                 }
                 if (!isUserUnlocked()) {
                     Log.e(TAG, "Storage locked, " + type + " failed");
                     return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
                 }
                 // Block until all packets have been send.
-                return sendConvoListingRsp(op, appParams,name);
+                return sendMessageListingRsp(op, appParams, name);
+
+            } else if (type.equals(TYPE_GET_CONVO_LISTING)) {
+                name = (String) request.getHeader(HeaderSet.NAME);
+                if (V && appParams != null) {
+                    Log.d(TAG, "TYPE_GET_CONVO_LISTING: name is" + name + ", MaxListCount = "
+                            + appParams.getMaxListCount() + ", ListStartOffset = "
+                            + appParams.getStartOffset());
+                    Log.d(TAG,
+                            "FilterLastActivityBegin = " + appParams.getFilterLastActivityBegin());
+                    Log.d(TAG, "FilterLastActivityEnd = " + appParams.getFilterLastActivityEnd());
+                    Log.d(TAG, "FilterReadStatus = " + appParams.getFilterReadStatus());
+                    Log.d(TAG, "FilterRecipient = " + appParams.getFilterRecipient());
+                }
+                if (!isUserUnlocked()) {
+                    Log.e(TAG, "Storage locked, " + type + " failed");
+                    return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+                }
+                // Block until all packets have been send.
+                return sendConvoListingRsp(op, appParams, name);
             } else if (type.equals(TYPE_GET_MAS_INSTANCE_INFORMATION)) {
-                if(V && appParams != null) {
-                    Log.d(TAG,"TYPE_MESSAGE (GET): MASInstandeId = "
-                            + appParams.getMasInstanceId());
+                if (V && appParams != null) {
+                    Log.d(TAG,
+                            "TYPE_MESSAGE (GET): MASInstandeId = " + appParams.getMasInstanceId());
                 }
                 // Block until all packets have been send.
                 return sendMASInstanceInformationRsp(op, appParams);
-            } else if (type.equals(TYPE_MESSAGE)){
-                name = (String)request.getHeader(HeaderSet.NAME);
-                if(V && appParams != null) {
-                    Log.d(TAG,"TYPE_MESSAGE (GET): name is" + name +
-                            ", Attachment = " + appParams.getAttachment() +
-                            ", Charset = " + appParams.getCharset() +
-                            ", FractionRequest = " + appParams.getFractionRequest());
+            } else if (type.equals(TYPE_MESSAGE)) {
+                name = (String) request.getHeader(HeaderSet.NAME);
+                if (V && appParams != null) {
+                    Log.d(TAG, "TYPE_MESSAGE (GET): name is" + name + ", Attachment = "
+                            + appParams.getAttachment() + ", Charset = " + appParams.getCharset()
+                            + ", FractionRequest = " + appParams.getFractionRequest());
                 }
                 if (!isUserUnlocked()) {
                     Log.e(TAG, "Storage locked, " + type + " failed");
@@ -956,13 +1101,15 @@
             Log.e(TAG, "Exception:", e);
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
         } catch (Exception e) {
-            if(D) {
-                Log.e(TAG, "Exception occured while handling request",e);
+            if (D) {
+                Log.e(TAG, "Exception occured while handling request", e);
             } else {
                 Log.e(TAG, "Exception occured while handling request");
             }
-            if(mIsAborted == true) {
-                if(D) Log.d(TAG, "onGet Operation Aborted");
+            if (mIsAborted) {
+                if (D) {
+                    Log.d(TAG, "onGet Operation Aborted");
+                }
                 return ResponseCodes.OBEX_HTTP_OK;
             } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
@@ -983,9 +1130,8 @@
      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
      */
-    private int sendMessageListingRsp(Operation op,
-                                      BluetoothMapAppParams appParams,
-                                      String folderName){
+    private int sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams,
+            String folderName) {
         OutputStream outStream = null;
         byte[] outBytes = null;
         int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize;
@@ -993,7 +1139,7 @@
         HeaderSet replyHeaders = new HeaderSet();
         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
         BluetoothMapMessageListing outList;
-        if(appParams == null){
+        if (appParams == null) {
             appParams = new BluetoothMapAppParams();
             appParams.setMaxListCount(1024);
             appParams.setStartOffset(0);
@@ -1010,41 +1156,40 @@
          */
         BluetoothMapFolderElement folderToList = null;
         if (appParams.getFilterMsgHandle() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER
-            || appParams.getFilterConvoId() != null) {
+                || appParams.getFilterConvoId() != null) {
             // If messageHandle or convoId filtering ignore folder
-            Log.v(TAG,"sendMessageListingRsp: ignore folder ");
+            Log.v(TAG, "sendMessageListingRsp: ignore folder ");
             folderToList = mCurrentFolder.getRoot();
             folderToList.setIngore(true);
         } else {
             folderToList = getFolderElementFromName(folderName);
-            if(folderToList == null) {
-                Log.w(TAG,"sendMessageListingRsp: folderToList == "+
-                        "null-sending OBEX_HTTP_BAD_REQUEST");
+            if (folderToList == null) {
+                Log.w(TAG, "sendMessageListingRsp: folderToList == "
+                        + "null-sending OBEX_HTTP_BAD_REQUEST");
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
-            Log.v(TAG,"sendMessageListingRsp: has sms " + folderToList.hasSmsMmsContent() +
-                    ", has email " + folderToList.hasEmailContent() +
-                    ", has IM " + folderToList.hasImContent() );
+            Log.v(TAG, "sendMessageListingRsp: has sms " + folderToList.hasSmsMmsContent()
+                    + ", has email " + folderToList.hasEmailContent() + ", has IM "
+                    + folderToList.hasImContent());
         }
 
         try {
-            // Open the OBEX body stream
-            outStream = op.openOutputStream();
-
-            if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+            if (appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 appParams.setMaxListCount(1024);
+            }
 
-            if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+            if (appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 appParams.setStartOffset(0);
+            }
 
             // Check to see if we only need to send the size - hence no need to encode.
-            if(appParams.getMaxListCount() != 0) {
+            if (appParams.getMaxListCount() != 0) {
                 outList = mOutContent.msgListing(folderToList, appParams);
                 // Generate the byte stream
                 outAppParams.setMessageListingSize(outList.getCount());
                 String version;
-                if(0 < (mRemoteFeatureMask &
-                        BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)) {
+                if (0 < (mRemoteFeatureMask
+                        & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)) {
                     version = BluetoothMapUtils.MAP_V11_STR;
                 } else {
                     version = BluetoothMapUtils.MAP_V10_STR;
@@ -1062,62 +1207,88 @@
             folderToList.setIngore(false);
             // Build the application parameter header
             // let the peer know if there are unread messages in the list
-            if(hasUnread) {
+            if (hasUnread) {
                 outAppParams.setNewMessage(1);
-            }else{
+            } else {
                 outAppParams.setNewMessage(0);
             }
             if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT)
-                    == BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT ) {
+                    == BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT) {
                 outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
             }
-            if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT)
+            if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT)
                     == BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT) {
                 // Force update of version counter if needed
                 mObserver.refreshFolderVersionCounter();
                 outAppParams.setFolderVerCounter(mMasInstance.getFolderVersionCounter(), 0);
             }
             outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
-            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
+            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.encodeParams());
             op.sendHeaders(replyHeaders);
 
+            // Open the OBEX body stream
+            outStream = op.openOutputStream();
         } catch (IOException e) {
-            Log.w(TAG,"sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
-            if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
-            if(mIsAborted == true) {
-                if(D) Log.d(TAG, "sendMessageListingRsp Operation Aborted");
+            Log.w(TAG, "sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException ex) {
+                }
+            }
+            if (mIsAborted) {
+                if (D) {
+                    Log.d(TAG, "sendMessageListingRsp Operation Aborted");
+                }
                 return ResponseCodes.OBEX_HTTP_OK;
             } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } catch (IllegalArgumentException e) {
-            Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException"+
-                                            " - sending OBEX_HTTP_BAD_REQUEST", e);
-            if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
+            Log.w(TAG, "sendMessageListingRsp: IllegalArgumentException"
+                    + " - sending OBEX_HTTP_BAD_REQUEST", e);
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException ex) {
+                }
+            }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
         }
 
         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
-        if(outBytes != null) {
+        if (outBytes != null) {
             try {
-                while (bytesWritten < outBytes.length && mIsAborted == false) {
+                while (bytesWritten < outBytes.length && !mIsAborted) {
                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
                     outStream.write(outBytes, bytesWritten, bytesToWrite);
                     bytesWritten += bytesToWrite;
                 }
             } catch (IOException e) {
-                if(D) Log.w(TAG,e);
+                if (D) {
+                    Log.w(TAG, e);
+                }
                 // We were probably aborted or disconnected
             } finally {
-                if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+                if (outStream != null) {
+                    try {
+                        outStream.close();
+                    } catch (IOException e) {
+                    }
+                }
             }
-            if(bytesWritten != outBytes.length && !mIsAborted) {
-                Log.w(TAG,"sendMessageListingRsp: bytesWritten != outBytes.length" +
-                        " - sending OBEX_HTTP_BAD_REQUEST");
+            if (bytesWritten != outBytes.length && !mIsAborted) {
+                Log.w(TAG, "sendMessageListingRsp: bytesWritten != outBytes.length"
+                        + " - sending OBEX_HTTP_BAD_REQUEST");
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } else {
-            if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException e) {
+                }
+            }
         }
         return ResponseCodes.OBEX_HTTP_OK;
     }
@@ -1132,27 +1303,27 @@
      */
     private void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
         int masFilterMask = 0;
-        if(!mEnableSmsMms) {
+        if (!mEnableSmsMms) {
             masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
             masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_GSM;
             masFilterMask |= BluetoothMapAppParams.FILTER_NO_MMS;
         }
-        if(mAccount==null){
+        if (mAccount == null) {
             masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
             masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
         } else {
-            if(!(mAccount.getType() == BluetoothMapUtils.TYPE.EMAIL)) {
+            if (!(mAccount.getType() == BluetoothMapUtils.TYPE.EMAIL)) {
                 masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
             }
-            if(!(mAccount.getType() == BluetoothMapUtils.TYPE.IM)) {
+            if (!(mAccount.getType() == BluetoothMapUtils.TYPE.IM)) {
                 masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
             }
         }
-        if(overwrite) {
+        if (overwrite) {
             appParams.setFilterMessageType(masFilterMask);
         } else {
             int newMask = appParams.getFilterMessageType();
-            if(newMask == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
+            if (newMask == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 appParams.setFilterMessageType(newMask);
             } else {
                 newMask |= masFilterMask;
@@ -1174,9 +1345,8 @@
      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
      */
-    private int sendConvoListingRsp(Operation op,
-                                    BluetoothMapAppParams appParams,
-                                    String folderName){
+    private int sendConvoListingRsp(Operation op, BluetoothMapAppParams appParams,
+            String folderName) {
         OutputStream outStream = null;
         byte[] outBytes = null;
         int maxChunkSize, bytesToWrite, bytesWritten = 0;
@@ -1184,7 +1354,7 @@
         HeaderSet replyHeaders = new HeaderSet();
         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
         BluetoothMapConvoListing outList;
-        if(appParams == null){
+        if (appParams == null) {
             appParams = new BluetoothMapAppParams();
             appParams.setMaxListCount(1024);
             appParams.setStartOffset(0);
@@ -1195,87 +1365,116 @@
 
         // Check to see if we only need to send the size - hence no need to encode.
         try {
-            // Open the OBEX body stream
-            outStream = op.openOutputStream();
-
-            if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+            if (appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 appParams.setMaxListCount(1024);
+            }
 
-            if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+            if (appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 appParams.setStartOffset(0);
+            }
 
-            if(appParams.getMaxListCount() != 0) {
+            if (appParams.getMaxListCount() != 0) {
                 outList = mOutContent.convoListing(appParams, false);
                 outAppParams.setConvoListingSize(outList.getCount());
                 // Generate the byte stream
                 outBytes = outList.encode(); // Include thread ID for clients that supports it.
-                if(D) Log.d(TAG, "outBytes size:"+ outBytes.length);
+                if (D) {
+                    Log.d(TAG, "outBytes size:" + outBytes.length);
+                }
             } else {
                 outList = mOutContent.convoListing(appParams, true);
                 outAppParams.setConvoListingSize(outList.getCount());
-                if(mEnableSmsMms) {
+                if (mEnableSmsMms) {
                     mOutContent.refreshSmsMmsConvoVersions();
                 }
-                if(mAccount != null) {
+                if (mAccount != null) {
                     mOutContent.refreshImEmailConvoVersions();
                 }
                 // Force update of version counter if needed
                 mObserver.refreshConvoListVersionCounter();
-                if(0 < (mRemoteFeatureMask &
-                        BluetoothMapUtils.MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT)) {
+                if (0 < (mRemoteFeatureMask
+                        & BluetoothMapUtils.MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT)) {
                     outAppParams.setConvoListingVerCounter(
                             mMasInstance.getCombinedConvoListVersionCounter(), 0);
                 }
                 op.noBodyHeader();
             }
-            if(D) Log.d(TAG, "outList size:"+ outList.getCount()
-                    + " MaxListCount: "+appParams.getMaxListCount());
+            if (D) {
+                Log.d(TAG, "outList size:" + outList.getCount() + " MaxListCount: "
+                        + appParams.getMaxListCount());
+            }
             outList = null; // We don't need it anymore - we might as well give it up for GC
             outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
 
             // Build the application parameter header
             // The MseTime is not in the CR - but I think it is missing.
             outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
-            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
+            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.encodeParams());
             op.sendHeaders(replyHeaders);
 
+            // Open the OBEX body stream
+            outStream = op.openOutputStream();
         } catch (IOException e) {
-            Log.w(TAG,"sendConvoListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
-            if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
-            if(mIsAborted == true) {
-                if(D) Log.d(TAG, "sendConvoListingRsp Operation Aborted");
+            Log.w(TAG, "sendConvoListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException ex) {
+                }
+            }
+            if (mIsAborted) {
+                if (D) {
+                    Log.d(TAG, "sendConvoListingRsp Operation Aborted");
+                }
                 return ResponseCodes.OBEX_HTTP_OK;
             } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } catch (IllegalArgumentException e) {
-            Log.w(TAG,"sendConvoListingRsp: IllegalArgumentException" +
-                    " - sending OBEX_HTTP_BAD_REQUEST", e);
-            if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
+            Log.w(TAG, "sendConvoListingRsp: IllegalArgumentException"
+                    + " - sending OBEX_HTTP_BAD_REQUEST", e);
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException ex) {
+                }
+            }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
         }
 
         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
-        if(outBytes != null) {
+        if (outBytes != null) {
             try {
-                while (bytesWritten < outBytes.length && mIsAborted == false) {
+                while (bytesWritten < outBytes.length && !mIsAborted) {
                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
                     outStream.write(outBytes, bytesWritten, bytesToWrite);
                     bytesWritten += bytesToWrite;
                 }
             } catch (IOException e) {
-                if(D) Log.w(TAG,e);
+                if (D) {
+                    Log.w(TAG, e);
+                }
                 // We were probably aborted or disconnected
             } finally {
-                if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+                if (outStream != null) {
+                    try {
+                        outStream.close();
+                    } catch (IOException e) {
+                    }
+                }
             }
-            if(bytesWritten != outBytes.length && !mIsAborted) {
-                Log.w(TAG,"sendConvoListingRsp: bytesWritten != outBytes.length" +
-                        " - sending OBEX_HTTP_BAD_REQUEST");
+            if (bytesWritten != outBytes.length && !mIsAborted) {
+                Log.w(TAG, "sendConvoListingRsp: bytesWritten != outBytes.length"
+                        + " - sending OBEX_HTTP_BAD_REQUEST");
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } else {
-            if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException e) {
+                }
+            }
         }
         return ResponseCodes.OBEX_HTTP_OK;
     }
@@ -1293,35 +1492,36 @@
      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
      */
-    private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){
+    private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams) {
         OutputStream outStream = null;
         byte[] outBytes = null;
         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
         int maxChunkSize, bytesWritten = 0;
         HeaderSet replyHeaders = new HeaderSet();
         int bytesToWrite, maxListCount, listStartOffset;
-        if(appParams == null){
+        if (appParams == null) {
             appParams = new BluetoothMapAppParams();
             appParams.setMaxListCount(1024);
         }
 
-        if(V)
-            Log.v(TAG,"sendFolderList for " + mCurrentFolder.getName());
+        if (V) {
+            Log.v(TAG, "sendFolderList for " + mCurrentFolder.getName());
+        }
 
         try {
             maxListCount = appParams.getMaxListCount();
             listStartOffset = appParams.getStartOffset();
 
-            if(listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+            if (listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 listStartOffset = 0;
+            }
 
-            if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
+            if (maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
                 maxListCount = 1024;
+            }
 
-            if(maxListCount != 0)
-            {
+            if (maxListCount != 0) {
                 outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
-                outStream = op.openOutputStream();
             } else {
                 // ESR08 specified that this shall only be included for MaxListCount=0
                 outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount());
@@ -1329,31 +1529,46 @@
             }
 
             // Build and set the application parameter header
-            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
+            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.encodeParams());
             op.sendHeaders(replyHeaders);
 
+            if (maxListCount != 0) {
+                outStream = op.openOutputStream();
+            }
         } catch (IOException e1) {
-            Log.w(TAG,"sendFolderListingRsp: IOException" +
-                    " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
-            if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
-            if(mIsAborted == true) {
-                if(D) Log.d(TAG, "sendFolderListingRsp Operation Aborted");
+            Log.w(TAG, "sendFolderListingRsp: IOException"
+                    + " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException e) {
+                }
+            }
+            if (mIsAborted) {
+                if (D) {
+                    Log.d(TAG, "sendFolderListingRsp Operation Aborted");
+                }
                 return ResponseCodes.OBEX_HTTP_OK;
             } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } catch (IllegalArgumentException e1) {
-            Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException" +
-                    " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
-            if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+            Log.w(TAG, "sendFolderListingRsp: IllegalArgumentException"
+                    + " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException e) {
+                }
+            }
             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
         }
 
         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
 
-        if(outBytes != null) {
+        if (outBytes != null) {
             try {
-                while (bytesWritten < outBytes.length && mIsAborted == false) {
+                while (bytesWritten < outBytes.length && !mIsAborted) {
                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
                     outStream.write(outBytes, bytesWritten, bytesToWrite);
                     bytesWritten += bytesToWrite;
@@ -1361,14 +1576,22 @@
             } catch (IOException e) {
                 // We were probably aborted or disconnected
             } finally {
-                if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+                if (outStream != null) {
+                    try {
+                        outStream.close();
+                    } catch (IOException e) {
+                    }
+                }
             }
-            if(V)
-                Log.v(TAG,"sendFolderList sent " + bytesWritten+" bytes out of "+ outBytes.length);
-            if(bytesWritten == outBytes.length || mIsAborted)
+            if (V) {
+                Log.v(TAG,
+                        "sendFolderList sent " + bytesWritten + " bytes out of " + outBytes.length);
+            }
+            if (bytesWritten == outBytes.length || mIsAborted) {
                 return ResponseCodes.OBEX_HTTP_OK;
-            else
+            } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
         }
 
         return ResponseCodes.OBEX_HTTP_OK;
@@ -1384,7 +1607,7 @@
      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
      */
-    private int sendMASInstanceInformationRsp(Operation op, BluetoothMapAppParams appParams){
+    private int sendMASInstanceInformationRsp(Operation op, BluetoothMapAppParams appParams) {
 
         OutputStream outStream = null;
         byte[] outBytes = null;
@@ -1392,26 +1615,26 @@
         int maxChunkSize, bytesToWrite, bytesWritten = 0;
 
         try {
-            if(mMasId == appParams.getMasInstanceId()) {
-                if(mAccount != null) {
-                    if(mAccount.getType() == TYPE.EMAIL) {
-                        outString = (mAccount.getName() != null) ? mAccount.getName() :
-                            BluetoothMapMasInstance.TYPE_EMAIL_STR;
-                    } else if(mAccount.getType() == TYPE.IM){
+            if (mMasId == appParams.getMasInstanceId()) {
+                if (mAccount != null) {
+                    if (mAccount.getType() == TYPE.EMAIL) {
+                        outString = (mAccount.getName() != null) ? mAccount.getName()
+                                : BluetoothMapMasInstance.TYPE_EMAIL_STR;
+                    } else if (mAccount.getType() == TYPE.IM) {
                         outString = mAccount.getUciFull();
-                        if(outString == null) {
+                        if (outString == null) {
                             String uci = mAccount.getUci();
                             // TODO: Do we need to align this with HF/PBAP
                             StringBuilder sb =
                                     new StringBuilder(uci == null ? 5 : 5 + uci.length());
                             sb.append("un");
-                            if(mMasId < 10) {
+                            if (mMasId < 10) {
                                 sb.append("00");
-                            } else if(mMasId < 100) {
+                            } else if (mMasId < 100) {
                                 sb.append("0");
                             }
                             sb.append(mMasId);
-                            if(uci != null) {
+                            if (uci != null) {
                                 sb.append(":").append(uci);
                             }
                             outString = sb.toString();
@@ -1433,10 +1656,12 @@
             outStream = op.openOutputStream();
 
         } catch (IOException e) {
-            Log.w(TAG,"sendMASInstanceInformationRsp: IOException" +
-                    " - sending OBEX_HTTP_BAD_REQUEST", e);
-            if(mIsAborted == true) {
-                if(D) Log.d(TAG, "sendMASInstanceInformationRsp Operation Aborted");
+            Log.w(TAG, "sendMASInstanceInformationRsp: IOException"
+                    + " - sending OBEX_HTTP_BAD_REQUEST", e);
+            if (mIsAborted) {
+                if (D) {
+                    Log.d(TAG, "sendMASInstanceInformationRsp Operation Aborted");
+                }
                 return ResponseCodes.OBEX_HTTP_OK;
             } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
@@ -1445,9 +1670,9 @@
 
         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
 
-        if(outBytes != null) {
+        if (outBytes != null) {
             try {
-                while (bytesWritten < outBytes.length && mIsAborted == false) {
+                while (bytesWritten < outBytes.length && !mIsAborted) {
                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
                     outStream.write(outBytes, bytesWritten, bytesToWrite);
                     bytesWritten += bytesToWrite;
@@ -1455,15 +1680,22 @@
             } catch (IOException e) {
                 // We were probably aborted or disconnected
             } finally {
-                if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+                if (outStream != null) {
+                    try {
+                        outStream.close();
+                    } catch (IOException e) {
+                    }
+                }
             }
-            if(V)
-                Log.v(TAG,"sendMASInstanceInformationRsp sent " + bytesWritten +
-                        " bytes out of "+ outBytes.length);
-            if(bytesWritten == outBytes.length || mIsAborted)
+            if (V) {
+                Log.v(TAG, "sendMASInstanceInformationRsp sent " + bytesWritten + " bytes out of "
+                        + outBytes.length);
+            }
+            if (bytesWritten == outBytes.length || mIsAborted) {
                 return ResponseCodes.OBEX_HTTP_OK;
-            else
+            } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
         }
         return ResponseCodes.OBEX_HTTP_OK;
     }
@@ -1483,69 +1715,89 @@
      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
      */
-    private int sendGetMessageRsp(Operation op, String handle,
-            BluetoothMapAppParams appParams, String version){
+    private int sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams,
+            String version) {
         OutputStream outStream = null;
         byte[] outBytes = null;
         int maxChunkSize, bytesToWrite, bytesWritten = 0;
 
         try {
             outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder, version);
-            outStream = op.openOutputStream();
 
             // If it is a fraction request of Email message, set header before responding
-            if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)||
-                    (BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.IM))) &&
-                    (appParams.getFractionRequest() ==
-                    BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) {
-                BluetoothMapAppParams outAppParams  = new BluetoothMapAppParams();
+            if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)
+                    || (BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.IM))) && (
+                    appParams.getFractionRequest()
+                            == BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) {
+                BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
                 HeaderSet replyHeaders = new HeaderSet();
                 outAppParams.setFractionDeliver(BluetoothMapAppParams.FRACTION_DELIVER_LAST);
                 // Build and set the application parameter header
                 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER,
-                        outAppParams.EncodeParams());
+                        outAppParams.encodeParams());
                 op.sendHeaders(replyHeaders);
-                if(V) Log.v(TAG,"sendGetMessageRsp fractionRequest - " +
-                        "set FRACTION_DELIVER_LAST header");
+                if (V) {
+                    Log.v(TAG, "sendGetMessageRsp fractionRequest - "
+                            + "set FRACTION_DELIVER_LAST header");
+                }
             }
+            outStream = op.openOutputStream();
 
         } catch (IOException e) {
-            Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
-            if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
-            if(mIsAborted == true) {
-                if(D) Log.d(TAG, "sendGetMessageRsp Operation Aborted");
+            Log.w(TAG, "sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException ex) {
+                }
+            }
+            if (mIsAborted) {
+                if (D) {
+                    Log.d(TAG, "sendGetMessageRsp Operation Aborted");
+                }
                 return ResponseCodes.OBEX_HTTP_OK;
             } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } catch (IllegalArgumentException e) {
-            Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - " +
-                    "sending OBEX_HTTP_BAD_REQUEST", e);
-            if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
+            Log.w(TAG, "sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - "
+                    + "sending OBEX_HTTP_BAD_REQUEST", e);
+            if (outStream != null) {
+                try {
+                    outStream.close();
+                } catch (IOException ex) {
+                }
+            }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
         }
 
         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
 
-        if(outBytes != null) {
+        if (outBytes != null) {
             try {
-                while (bytesWritten < outBytes.length && mIsAborted == false) {
+                while (bytesWritten < outBytes.length && !mIsAborted) {
                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
                     outStream.write(outBytes, bytesWritten, bytesToWrite);
                     bytesWritten += bytesToWrite;
                 }
             } catch (IOException e) {
                 // We were probably aborted or disconnected
-                if(D && e.getMessage().equals("Abort Received")) {
+                if (D && e.getMessage().equals("Abort Received")) {
                     Log.w(TAG, "getMessage() Aborted...", e);
                 }
             } finally {
-                if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
+                if (outStream != null) {
+                    try {
+                        outStream.close();
+                    } catch (IOException e) {
+                    }
+                }
             }
-            if(bytesWritten == outBytes.length || mIsAborted)
+            if (bytesWritten == outBytes.length || mIsAborted) {
                 return ResponseCodes.OBEX_HTTP_OK;
-            else
+            } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
         }
 
         return ResponseCodes.OBEX_HTTP_OK;
@@ -1553,7 +1805,9 @@
 
     @Override
     public int onDelete(HeaderSet request, HeaderSet reply) {
-        if(D) Log.v(TAG, "onDelete() " + request.toString());
+        if (D) {
+            Log.v(TAG, "onDelete() " + request.toString());
+        }
         mIsAborted = false;
         notifyUpdateWakeLock();
         String type, name;
@@ -1562,48 +1816,51 @@
 
         /* TODO: If this is to be placed here, we need to cleanup - e.g. the exception handling */
         try {
-            type = (String)request.getHeader(HeaderSet.TYPE);
+            type = (String) request.getHeader(HeaderSet.TYPE);
 
-            name = (String)request.getHeader(HeaderSet.NAME);
-            appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
-            if(appParamRaw != null)
+            name = (String) request.getHeader(HeaderSet.NAME);
+            appParamRaw = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER);
+            if (appParamRaw != null) {
                 appParams = new BluetoothMapAppParams(appParamRaw);
-            if(D) Log.d(TAG,"type = " + type + ", name = " + name);
-            if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
-                if(V) {
-                    Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
+            }
+            if (D) {
+                Log.d(TAG, "type = " + type + ", name = " + name);
+            }
+            if (type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
+                if (V) {
+                    Log.d(TAG, "TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
                             + appParams.getNotificationFilter());
                 }
                 mObserver.setNotificationFilter(appParams.getNotificationFilter());
                 return ResponseCodes.OBEX_HTTP_OK;
-            }  else if (type.equals(TYPE_SET_OWNER_STATUS)) {
-                if(V) {
-                    Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
-                          " PresenceAvailability " + appParams.getPresenceAvailability() +
-                          ", PresenceStatus: " + appParams.getPresenceStatus() +
-                          ", LastActivity: " + appParams.getLastActivityString() +
-                          ", ChatStatus: " + appParams.getChatState() +
-                          ", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
+            } else if (type.equals(TYPE_SET_OWNER_STATUS)) {
+                if (V) {
+                    Log.d(TAG, "TYPE_SET_OWNER_STATUS:" + " PresenceAvailability "
+                            + appParams.getPresenceAvailability() + ", PresenceStatus: " + appParams
+                            .getPresenceStatus() + ", LastActivity: "
+                            + appParams.getLastActivityString() + ", ChatStatus: "
+                            + appParams.getChatState() + ", ChatStatusConvoId: "
+                            + appParams.getChatStateConvoIdString());
                 }
                 return setOwnerStatus(name, appParams);
             }
 
-        } catch (RemoteException e){
+        } catch (RemoteException e) {
             //reload the providerClient and return error
             try {
                 mProviderClient = acquireUnstableContentProviderOrThrow();
-            }catch (RemoteException e2){
+            } catch (RemoteException e2) {
                 //should not happen
             }
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
-        }catch (Exception e) {
+        } catch (Exception e) {
 
-            if(D) {
-                Log.e(TAG, "Exception occured while handling request",e);
+            if (D) {
+                Log.e(TAG, "Exception occured while handling request", e);
             } else {
                 Log.e(TAG, "Exception occured while handling request");
             }
-            if(mIsAborted) {
+            if (mIsAborted) {
                 return ResponseCodes.OBEX_HTTP_OK;
             } else {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
@@ -1613,14 +1870,14 @@
     }
 
     private void notifyUpdateWakeLock() {
-        if(mCallback != null) {
+        if (mCallback != null) {
             Message msg = Message.obtain(mCallback);
             msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
             msg.sendToTarget();
         }
     }
 
-    private static final void logHeader(HeaderSet hs) {
+    private static void logHeader(HeaderSet hs) {
         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
         try {
             Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID));
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index dd822df..bfb1d3f 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -37,15 +37,16 @@
 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;
 
+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.btservice.ProfileService.IProfileServiceBinder;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -75,49 +76,36 @@
             "com.android.bluetooth.map.USER_CONFIRM_TIMEOUT";
     private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000;
 
-    /** Intent indicating that the email settings activity should be opened*/
-    public static final String ACTION_SHOW_MAPS_SETTINGS =
+    // Intent indicating that the email settings activity should be opened
+    static final String ACTION_SHOW_MAPS_SETTINGS =
             "android.btmap.intent.action.SHOW_MAPS_SETTINGS";
 
-    public static final int MSG_SERVERSESSION_CLOSE = 5000;
-
-    public static final int MSG_SESSION_ESTABLISHED = 5001;
-
-    public static final int MSG_SESSION_DISCONNECTED = 5002;
-
-    public static final int MSG_MAS_CONNECT = 5003; // Send at MAS connect, including the MAS_ID
-    public static final int MSG_MAS_CONNECT_CANCEL = 5004; // Send at auth. declined
-
-    public static final int MSG_ACQUIRE_WAKE_LOCK = 5005;
-
-    public static final int MSG_RELEASE_WAKE_LOCK = 5006;
-
-    public static final int MSG_MNS_SDP_SEARCH = 5007;
-
-    public static final int MSG_OBSERVER_REGISTRATION = 5008;
+    static final int MSG_SERVERSESSION_CLOSE = 5000;
+    static final int MSG_SESSION_ESTABLISHED = 5001;
+    static final int MSG_SESSION_DISCONNECTED = 5002;
+    static final int MSG_MAS_CONNECT = 5003; // Send at MAS connect, including the MAS_ID
+    static final int MSG_MAS_CONNECT_CANCEL = 5004; // Send at auth. declined
+    static final int MSG_ACQUIRE_WAKE_LOCK = 5005;
+    static final int MSG_RELEASE_WAKE_LOCK = 5006;
+    static final int MSG_MNS_SDP_SEARCH = 5007;
+    static final int MSG_OBSERVER_REGISTRATION = 5008;
 
     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
-
     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
 
     private static final int START_LISTENER = 1;
-
     private static final int USER_TIMEOUT = 2;
-
     private static final int DISCONNECT_MAP = 3;
-
     private static final int SHUTDOWN = 4;
-
-    private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
-
-    private PowerManager.WakeLock mWakeLock = null;
-
     private static final int UPDATE_MAS_INSTANCES = 5;
 
-    public static final int UPDATE_MAS_INSTANCES_ACCOUNT_ADDED = 0;
-    public static final int UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED = 1;
-    public static final int UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED = 2;
-    public static final int UPDATE_MAS_INSTANCES_ACCOUNT_DISCONNECT = 3;
+    private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
+    private PowerManager.WakeLock mWakeLock = null;
+
+    static final int UPDATE_MAS_INSTANCES_ACCOUNT_ADDED = 0;
+    static final int UPDATE_MAS_INSTANCES_ACCOUNT_REMOVED = 1;
+    static final int UPDATE_MAS_INSTANCES_ACCOUNT_RENAMED = 2;
+    static final int UPDATE_MAS_INSTANCES_ACCOUNT_DISCONNECT = 3;
 
     private static final int MAS_ID_SMS_MMS = 0;
 
@@ -125,14 +113,15 @@
 
     private BluetoothMnsObexClient mBluetoothMnsObexClient = null;
 
-    /* mMasInstances: A list of the active MasInstances with the key being the MasId */
+    // mMasInstances: A list of the active MasInstances using the MasId for the key
     private SparseArray<BluetoothMapMasInstance> mMasInstances =
             new SparseArray<BluetoothMapMasInstance>(1);
-    /* mMasInstanceMap: A list of the active MasInstances with the key being the account */
+    // mMasInstanceMap: A list of the active MasInstances using the account for the key
     private HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance> mMasInstanceMap =
             new HashMap<BluetoothMapAccountItem, BluetoothMapMasInstance>(1);
 
-    private static BluetoothDevice mRemoteDevice = null; // The remote connected device - protect access
+    // The remote connected device - protect access
+    private static BluetoothDevice sRemoteDevice = null;
 
     private ArrayList<BluetoothMapAccountItem> mEnabledAccounts = null;
     private static String sRemoteDeviceName = null;
@@ -147,35 +136,35 @@
     private int mPermission = BluetoothDevice.ACCESS_UNKNOWN;
     private boolean mAccountChanged = false;
     private boolean mSdpSearchInitiated = false;
-    SdpMnsRecord mMnsRecord = null;
+    private SdpMnsRecord mMnsRecord = null;
     private MapServiceMessageHandler mSessionStatusHandler;
-    private boolean mStartError = true;
+    private boolean mServiceStarted = false;
+
+    private static BluetoothMapService sBluetoothMapService;
 
     private boolean mSmsCapable = true;
 
     private static final ParcelUuid[] MAP_UUIDS = {
-        BluetoothUuid.MAP,
-        BluetoothUuid.MNS,
+            BluetoothUuid.MAP, BluetoothUuid.MNS,
     };
 
     public BluetoothMapService() {
         mState = BluetoothMap.STATE_DISCONNECTED;
     }
 
-
     private synchronized void closeService() {
-        if (DEBUG) Log.d(TAG, "MAP Service closeService in");
-
+        if (DEBUG) {
+            Log.d(TAG, "closeService() in");
+        }
         if (mBluetoothMnsObexClient != null) {
             mBluetoothMnsObexClient.shutdown();
             mBluetoothMnsObexClient = null;
         }
-        if (mMasInstances.size() > 0) {
-            for (int i=0, c=mMasInstances.size(); i < c; i++) {
-                mMasInstances.valueAt(i).shutdown();
-            }
-            mMasInstances.clear();
+        int numMasInstances = mMasInstances.size();
+        for (int i = 0; i < numMasInstances; i++) {
+            mMasInstances.valueAt(i).shutdown();
         }
+        mMasInstances.clear();
 
         mIsWaitingAuthorization = false;
         mPermission = BluetoothDevice.ACCESS_UNKNOWN;
@@ -183,42 +172,48 @@
 
         if (mWakeLock != null) {
             mWakeLock.release();
-            if (VERBOSE) Log.v(TAG, "CloseService(): Release Wake Lock");
+            if (VERBOSE) {
+                Log.v(TAG, "CloseService(): Release Wake Lock");
+            }
             mWakeLock = null;
         }
-        /* Only one SHUTDOWN message expected to closeService.
-         * Hence, quit looper and Handler on first SHUTDOWN message*/
-        if (mSessionStatusHandler != null) {
-            //Perform cleanup in Handler running on worker Thread
-            mSessionStatusHandler.removeCallbacksAndMessages(null);
-            Looper looper = mSessionStatusHandler.getLooper();
-            if (looper != null) {
-                looper.quit();
-                if(VERBOSE) Log.i(TAG, "Quit looper");
-            }
-            mSessionStatusHandler = null;
-            if(VERBOSE) Log.i(TAG, "Remove Handler");
-        }
-        mRemoteDevice = null;
 
-        if (VERBOSE) Log.v(TAG, "MAP Service closeService out");
+        sRemoteDevice = null;
+
+        if (mSessionStatusHandler == null) {
+            return;
+        }
+
+        // Perform cleanup in Handler running on worker Thread
+        mSessionStatusHandler.removeCallbacksAndMessages(null);
+        Looper looper = mSessionStatusHandler.getLooper();
+        if (looper != null) {
+            looper.quit();
+            if (VERBOSE) {
+                Log.i(TAG, "Quit looper");
+            }
+        }
+        mSessionStatusHandler = null;
+
+        if (VERBOSE) {
+            Log.v(TAG, "MAP Service closeService out");
+        }
     }
 
     /**
-     * Starts the RFComm listener threads for each MAS
-     * @throws IOException
+     * Starts the Socket listener threads for each MAS
      */
-    private final void startRfcommSocketListeners(int masId) {
-        if(masId == -1) {
-            for(int i=0, c=mMasInstances.size(); i < c; i++) {
-                mMasInstances.valueAt(i).startRfcommSocketListener();
+    private void startSocketListeners(int masId) {
+        if (masId == -1) {
+            for (int i = 0, c = mMasInstances.size(); i < c; i++) {
+                mMasInstances.valueAt(i).startSocketListeners();
             }
         } else {
             BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1
-            if(masInst != null) {
-                masInst.startRfcommSocketListener();
+            if (masInst != null) {
+                masInst.startSocketListeners();
             } else {
-                Log.w(TAG, "startRfcommSocketListeners(): Invalid MasId: " + masId);
+                Log.w(TAG, "startSocketListeners(): Invalid MasId: " + masId);
             }
         }
     }
@@ -226,50 +221,56 @@
     /**
      * Start a MAS instance for SMS/MMS and each e-mail account.
      */
-    private final void startObexServerSessions() {
-        if (DEBUG) Log.d(TAG, "Map Service START ObexServerSessions()");
-
-        // acquire the wakeLock before start Obex transaction thread
-        if (mWakeLock == null) {
-            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
-            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                    "StartingObexMapTransaction");
-            mWakeLock.setReferenceCounted(false);
-            mWakeLock.acquire();
-            if (VERBOSE) Log.v(TAG, "startObexSessions(): Acquire Wake Lock");
+    private void startObexServerSessions() {
+        if (DEBUG) {
+            Log.d(TAG, "Map Service START ObexServerSessions()");
         }
 
-        if(mBluetoothMnsObexClient == null) {
+        // Acquire the wakeLock before starting Obex transaction thread
+        if (mWakeLock == null) {
+            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+            mWakeLock =
+                    pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StartingObexMapTransaction");
+            mWakeLock.setReferenceCounted(false);
+            mWakeLock.acquire();
+            if (VERBOSE) {
+                Log.v(TAG, "startObexSessions(): Acquire Wake Lock");
+            }
+        }
+
+        if (mBluetoothMnsObexClient == null) {
             mBluetoothMnsObexClient =
-                    new BluetoothMnsObexClient(mRemoteDevice, mMnsRecord, mSessionStatusHandler);
+                    new BluetoothMnsObexClient(sRemoteDevice, mMnsRecord, mSessionStatusHandler);
         }
 
         boolean connected = false;
-        for(int i=0, c=mMasInstances.size(); i < c; i++) {
+        for (int i = 0, c = mMasInstances.size(); i < c; i++) {
             try {
-                if(mMasInstances.valueAt(i)
-                        .startObexServerSession(mBluetoothMnsObexClient) == true) {
+                if (mMasInstances.valueAt(i).startObexServerSession(mBluetoothMnsObexClient)) {
                     connected = true;
                 }
             } catch (IOException e) {
-                Log.w(TAG,"IOException occured while starting an obexServerSession restarting" +
-                        " the listener",e);
+                Log.w(TAG, "IOException occured while starting an obexServerSession restarting"
+                        + " the listener", e);
                 mMasInstances.valueAt(i).restartObexServerSession();
             } catch (RemoteException e) {
-                Log.w(TAG,"RemoteException occured while starting an obexServerSession restarting" +
-                        " the listener",e);
+                Log.w(TAG, "RemoteException occured while starting an obexServerSession restarting"
+                        + " the listener", e);
                 mMasInstances.valueAt(i).restartObexServerSession();
             }
         }
-        if(connected) {
+        if (connected) {
             setState(BluetoothMap.STATE_CONNECTED);
         }
 
         mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
-        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
-                .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
+        mSessionStatusHandler.sendMessageDelayed(
+                mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
+                RELEASE_WAKE_LOCK_DELAY);
 
-        if (VERBOSE) Log.v(TAG, "startObexServerSessions() success!");
+        if (VERBOSE) {
+            Log.v(TAG, "startObexServerSessions() success!");
+        }
     }
 
     public Handler getHandler() {
@@ -281,39 +282,41 @@
      * @param masId use -1 to stop all instances
      */
     private void stopObexServerSessions(int masId) {
-        if (DEBUG) Log.d(TAG, "MAP Service STOP ObexServerSessions()");
+        if (DEBUG) {
+            Log.d(TAG, "MAP Service STOP ObexServerSessions()");
+        }
 
         boolean lastMasInst = true;
 
-        if(masId != -1) {
-            for(int i=0, c=mMasInstances.size(); i < c; i++) {
+        if (masId != -1) {
+            for (int i = 0, c = mMasInstances.size(); i < c; i++) {
                 BluetoothMapMasInstance masInst = mMasInstances.valueAt(i);
-                if(masInst.getMasId() != masId && masInst.isStarted()) {
+                if (masInst.getMasId() != masId && masInst.isStarted()) {
                     lastMasInst = false;
                 }
             }
         } // Else just close down it all
 
-        /* Shutdown the MNS client - currently must happen before MAS close */
-        if(mBluetoothMnsObexClient != null && lastMasInst) {
+        // Shutdown the MNS client - this must happen before MAS close
+        if (mBluetoothMnsObexClient != null && lastMasInst) {
             mBluetoothMnsObexClient.shutdown();
             mBluetoothMnsObexClient = null;
         }
 
         BluetoothMapMasInstance masInst = mMasInstances.get(masId); // returns null for -1
-        if(masInst != null) {
+        if (masInst != null) {
             masInst.restartObexServerSession();
-        } else  if(masId == -1) {
-            for(int i=0, c=mMasInstances.size(); i < c; i++) {
+        } else if (masId == -1) {
+            for (int i = 0, c = mMasInstances.size(); i < c; i++) {
                 mMasInstances.valueAt(i).restartObexServerSession();
             }
         }
 
-        if(lastMasInst) {
+        if (lastMasInst) {
             setState(BluetoothMap.STATE_DISCONNECTED);
             mPermission = BluetoothDevice.ACCESS_UNKNOWN;
-            mRemoteDevice = null;
-            if(mAccountChanged) {
+            sRemoteDevice = null;
+            if (mAccountChanged) {
                 updateMasInstances(UPDATE_MAS_INSTANCES_ACCOUNT_DISCONNECT);
             }
         }
@@ -323,7 +326,9 @@
             mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
             mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
             mWakeLock.release();
-            if (VERBOSE) Log.v(TAG, "stopObexServerSessions(): Release Wake Lock");
+            if (VERBOSE) {
+                Log.v(TAG, "stopObexServerSessions(): Release Wake Lock");
+            }
         }
     }
 
@@ -331,18 +336,19 @@
         private MapServiceMessageHandler(Looper looper) {
             super(looper);
         }
+
         @Override
         public void handleMessage(Message msg) {
-            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
+            if (VERBOSE) {
+                Log.v(TAG, "Handler(): got msg=" + msg.what);
+            }
 
             switch (msg.what) {
                 case UPDATE_MAS_INSTANCES:
                     updateMasInstancesHandler();
                     break;
                 case START_LISTENER:
-                    if (mAdapter.isEnabled()) {
-                        startRfcommSocketListeners(msg.arg1);
-                    }
+                    startSocketListeners(msg.arg1);
                     break;
                 case MSG_MAS_CONNECT:
                     onConnectHandler(msg.arg1);
@@ -358,7 +364,7 @@
                     if (mIsWaitingAuthorization) {
                         Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
                         intent.setPackage(getString(R.string.pairing_ui_package));
-                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, sRemoteDevice);
                         intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
                                 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
                         sendBroadcast(intent);
@@ -376,48 +382,59 @@
                     // handled elsewhere
                     break;
                 case DISCONNECT_MAP:
-                    disconnectMap((BluetoothDevice)msg.obj);
+                    disconnectMap((BluetoothDevice) msg.obj);
                     break;
                 case SHUTDOWN:
-                    /* Ensure to call close from this handler to avoid starting new stuff
-                       because of pending messages */
+                    // Call close from this handler to avoid starting because of pending messages
                     closeService();
                     break;
                 case MSG_ACQUIRE_WAKE_LOCK:
-                    if (VERBOSE) Log.v(TAG, "Acquire Wake Lock request message");
+                    if (VERBOSE) {
+                        Log.v(TAG, "Acquire Wake Lock request message");
+                    }
                     if (mWakeLock == null) {
-                        PowerManager pm = (PowerManager)getSystemService(
-                                          Context.POWER_SERVICE);
+                        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
                         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                                    "StartingObexMapTransaction");
+                                "StartingObexMapTransaction");
                         mWakeLock.setReferenceCounted(false);
                     }
-                    if(!mWakeLock.isHeld()) {
+                    if (!mWakeLock.isHeld()) {
                         mWakeLock.acquire();
-                        if (DEBUG) Log.d(TAG, "  Acquired Wake Lock by message");
+                        if (DEBUG) {
+                            Log.d(TAG, "  Acquired Wake Lock by message");
+                        }
                     }
                     mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
-                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
-                      .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
+                    mSessionStatusHandler.sendMessageDelayed(
+                            mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
+                            RELEASE_WAKE_LOCK_DELAY);
                     break;
                 case MSG_RELEASE_WAKE_LOCK:
-                    if (VERBOSE) Log.v(TAG, "Release Wake Lock request message");
+                    if (VERBOSE) {
+                        Log.v(TAG, "Release Wake Lock request message");
+                    }
                     if (mWakeLock != null) {
                         mWakeLock.release();
-                        if (DEBUG) Log.d(TAG, "  Released Wake Lock by message");
+                        if (DEBUG) {
+                            Log.d(TAG, "  Released Wake Lock by message");
+                        }
                     }
                     break;
                 case MSG_MNS_SDP_SEARCH:
-                    if (mRemoteDevice != null) {
-                        if (DEBUG) Log.d(TAG,"MNS SDP Initiate Search ..");
-                        mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
+                    if (sRemoteDevice != null) {
+                        if (DEBUG) {
+                            Log.d(TAG, "MNS SDP Initiate Search ..");
+                        }
+                        sRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
                     } else {
                         Log.w(TAG, "remoteDevice info not available");
                     }
                     break;
                 case MSG_OBSERVER_REGISTRATION:
-                    if (DEBUG) Log.d(TAG,"ContentObserver Registration MASID: " + msg.arg1
-                        + " Enable: " + msg.arg2);
+                    if (DEBUG) {
+                        Log.d(TAG, "ContentObserver Registration MASID: " + msg.arg1 + " Enable: "
+                                + msg.arg2);
+                    }
                     BluetoothMapMasInstance masInst = mMasInstances.get(msg.arg1);
                     if (masInst != null && masInst.mObserver != null) {
                         try {
@@ -427,7 +444,7 @@
                                 masInst.mObserver.unregisterObserver();
                             }
                         } catch (RemoteException e) {
-                            Log.e(TAG,"ContentObserverRegistarion Failed: "+ e);
+                            Log.e(TAG, "ContentObserverRegistarion Failed: " + e);
                         }
                     }
                     break;
@@ -435,20 +452,23 @@
                     break;
             }
         }
-    };
+    }
 
     private void onConnectHandler(int masId) {
-        if (mIsWaitingAuthorization == true || mRemoteDevice == null
-                || mSdpSearchInitiated == true) {
+        if (mIsWaitingAuthorization || sRemoteDevice == null || mSdpSearchInitiated) {
             return;
         }
         BluetoothMapMasInstance masInst = mMasInstances.get(masId);
         // Need to ensure we are still allowed.
-        if (DEBUG) Log.d(TAG, "mPermission = " + mPermission);
+        if (DEBUG) {
+            Log.d(TAG, "mPermission = " + mPermission);
+        }
         if (mPermission == BluetoothDevice.ACCESS_ALLOWED) {
             try {
-                if (VERBOSE) Log.v(TAG, "incoming connection accepted from: "
-                        + sRemoteDeviceName + " automatically as trusted device");
+                if (VERBOSE) {
+                    Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName
+                            + " automatically as trusted device");
+                }
                 if (mBluetoothMnsObexClient != null && masInst != null) {
                     masInst.startObexServerSession(mBluetoothMnsObexClient);
                 } else {
@@ -466,80 +486,75 @@
         return mState;
     }
 
-    protected boolean isMapStarted() {
-        return !mStartError;
-    }
     public static BluetoothDevice getRemoteDevice() {
-        return mRemoteDevice;
+        return sRemoteDevice;
     }
+
     private void setState(int state) {
         setState(state, BluetoothMap.RESULT_SUCCESS);
     }
 
     private synchronized void setState(int state, int result) {
         if (state != mState) {
-            if (DEBUG) Log.d(TAG, "Map state " + mState + " -> " + state + ", result = "
-                    + result);
+            if (DEBUG) {
+                Log.d(TAG, "Map state " + mState + " -> " + state + ", result = " + result);
+            }
             int prevState = mState;
             mState = state;
             Intent intent = new Intent(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
             intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
             intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
-            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, sRemoteDevice);
             sendBroadcast(intent, BLUETOOTH_PERM);
         }
     }
 
-    public static String getRemoteDeviceName() {
-        return sRemoteDeviceName;
+    void disconnect(BluetoothDevice device) {
+        mSessionStatusHandler.sendMessage(
+                mSessionStatusHandler.obtainMessage(DISCONNECT_MAP, 0, 0, device));
     }
 
-    public boolean disconnect(BluetoothDevice device) {
-        mSessionStatusHandler.sendMessage(mSessionStatusHandler
-                .obtainMessage(DISCONNECT_MAP, 0, 0, device));
-        return true;
-    }
-
-    public boolean disconnectMap(BluetoothDevice device) {
-        boolean result = false;
-        if (DEBUG) Log.d(TAG, "disconnectMap");
-        if (getRemoteDevice()!= null && getRemoteDevice().equals(device)) {
+    void disconnectMap(BluetoothDevice device) {
+        if (DEBUG) {
+            Log.d(TAG, "disconnectMap");
+        }
+        if (getRemoteDevice() != null && getRemoteDevice().equals(device)) {
             switch (mState) {
                 case BluetoothMap.STATE_CONNECTED:
-                    /* Disconnect all connections and restart all MAS instances */
+                    // Disconnect all connections and restart all MAS instances
                     stopObexServerSessions(-1);
-                    result = true;
                     break;
                 default:
                     break;
-                }
+            }
         }
-        return result;
     }
 
-    public List<BluetoothDevice> getConnectedDevices() {
-        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-        synchronized(this) {
-            if (mState == BluetoothMap.STATE_CONNECTED && mRemoteDevice != null) {
-                devices.add(mRemoteDevice);
+    private List<BluetoothDevice> getConnectedDevices() {
+        List<BluetoothDevice> devices = new ArrayList<>();
+        synchronized (this) {
+            if (mState == BluetoothMap.STATE_CONNECTED && sRemoteDevice != null) {
+                devices.add(sRemoteDevice);
             }
         }
         return devices;
     }
 
-    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        List<BluetoothDevice> deviceList = new ArrayList<>();
         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
-        int connectionState;
+        if (bondedDevices == null) {
+            return deviceList;
+        }
         synchronized (this) {
             for (BluetoothDevice device : bondedDevices) {
                 ParcelUuid[] featureUuids = device.getUuids();
                 if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) {
                     continue;
                 }
-                connectionState = getConnectionState(device);
-                for(int i = 0; i < states.length; i++) {
-                    if (connectionState == states[i]) {
+                int connectionState = getConnectionState(device);
+                for (int state : states) {
+                    if (connectionState == state) {
                         deviceList.add(device);
                     }
                 }
@@ -548,8 +563,8 @@
         return deviceList;
     }
 
-    public int getConnectionState(BluetoothDevice device) {
-        synchronized(this) {
+    int getConnectionState(BluetoothDevice device) {
+        synchronized (this) {
             if (getState() == BluetoothMap.STATE_CONNECTED && getRemoteDevice().equals(device)) {
                 return BluetoothProfile.STATE_CONNECTED;
             } else {
@@ -558,19 +573,18 @@
         }
     }
 
-    public boolean setPriority(BluetoothDevice device, int priority) {
-        Settings.Global.putInt(getContentResolver(),
-            Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
-            priority);
-        if (VERBOSE) Log.v(TAG, "Saved priority " + device + " = " + priority);
-        return true;
+    boolean setPriority(BluetoothDevice device, int priority) {
+        if (VERBOSE) {
+            Log.v(TAG, "Saved priority " + device + " = " + priority);
+        }
+        return Settings.Global.putInt(getContentResolver(),
+                Settings.Global.getBluetoothMapPriorityKey(device.getAddress()), priority);
     }
 
-    public int getPriority(BluetoothDevice device) {
-        int priority = Settings.Global.getInt(getContentResolver(),
-            Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
-            BluetoothProfile.PRIORITY_UNDEFINED);
-        return priority;
+    int getPriority(BluetoothDevice device) {
+        return Settings.Global.getInt(getContentResolver(),
+                Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
+                BluetoothProfile.PRIORITY_UNDEFINED);
     }
 
     @Override
@@ -580,10 +594,8 @@
 
     @Override
     protected boolean start() {
-        if (DEBUG) Log.d(TAG, "start()");
-        if (isMapStarted()) {
-            Log.w(TAG, "start received for already started, ignoring");
-            return false;
+        if (DEBUG) {
+            Log.d(TAG, "start()");
         }
         HandlerThread thread = new HandlerThread("BluetoothMapHandler");
         thread.start();
@@ -592,7 +604,6 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
-        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
         filter.addAction(ACTION_SHOW_MAPS_SETTINGS);
@@ -601,43 +612,61 @@
         // We need two filters, since Type only applies to the ACTION_MESSAGE_SENT
         IntentFilter filterMessageSent = new IntentFilter();
         filterMessageSent.addAction(BluetoothMapContentObserver.ACTION_MESSAGE_SENT);
-        try{
+        try {
             filterMessageSent.addDataType("message/*");
         } catch (MalformedMimeTypeException e) {
             Log.e(TAG, "Wrong mime type!!!", e);
         }
         if (!mRegisteredMapReceiver) {
-            try {
-                registerReceiver(mMapReceiver, filter);
-                registerReceiver(mMapReceiver, filterMessageSent);
-                mRegisteredMapReceiver = true;
-            } catch (Exception e) {
-                Log.e(TAG,"Unable to register map receiver",e);
-            }
+            registerReceiver(mMapReceiver, filter);
+            registerReceiver(mMapReceiver, filterMessageSent);
+            mRegisteredMapReceiver = true;
         }
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         mAppObserver = new BluetoothMapAppObserver(this, this);
 
+        mSmsCapable = getResources().getBoolean(com.android.internal.R.bool.config_sms_capable);
+
         mEnabledAccounts = mAppObserver.getEnabledAccountItems();
+        createMasInstances();  // Uses mEnabledAccounts
 
-        mSmsCapable = getResources().getBoolean(
-                com.android.internal.R.bool.config_sms_capable);
-        // Uses mEnabledAccounts, hence getEnabledAccountItems() must be called before this.
-        createMasInstances();
-
-        // start RFCOMM listener
         sendStartListenerMessage(-1);
-        mStartError = false;
-        return !mStartError;
+        setBluetoothMapService(this);
+        mServiceStarted = true;
+        return true;
+    }
+
+    /**
+     * Get the current instance of {@link BluetoothMapService}
+     *
+     * @return current instance of {@link BluetoothMapService}
+     */
+    @VisibleForTesting
+    public static synchronized BluetoothMapService getBluetoothMapService() {
+        if (sBluetoothMapService == null) {
+            Log.w(TAG, "getBluetoothMapService(): service is null");
+            return null;
+        }
+        if (!sBluetoothMapService.isAvailable()) {
+            Log.w(TAG, "getBluetoothMapService(): service is not available");
+            return null;
+        }
+        return sBluetoothMapService;
+    }
+
+    private static synchronized void setBluetoothMapService(BluetoothMapService instance) {
+        if (DEBUG) {
+            Log.d(TAG, "setBluetoothMapService(): set to: " + instance);
+        }
+        sBluetoothMapService = instance;
     }
 
     /**
      * Call this to trigger an update of the MAS instance list.
      * No changes will be applied unless in disconnected state
      */
-    public void updateMasInstances(int action) {
-            mSessionStatusHandler.obtainMessage (UPDATE_MAS_INSTANCES,
-                    action, 0).sendToTarget();
+    void updateMasInstances(int action) {
+        mSessionStatusHandler.obtainMessage(UPDATE_MAS_INSTANCES, action, 0).sendToTarget();
     }
 
     /**
@@ -646,113 +675,101 @@
      * Will only make changes if state is disconnected.
      *
      * How it works:
-     * 1) Build lists of account changes from last update of mEnabledAccounts.
+     * 1) Build two lists of accounts
+     *      newAccountList - all accounts from mAppObserver
      *      newAccounts - accounts that have been enabled since mEnabledAccounts
      *                    was last updated.
-     *      removedAccounts - Accounts that is on mEnabledAccounts, but no longer
-     *                        enabled.
-     *      enabledAccounts - A new list of all enabled accounts.
-     * 2) Stop and remove all MasInstances on the remove list
+     *      mEnabledAccounts - The accounts which are left
+     * 2) Stop and remove all MasInstances in mEnabledAccounts
      * 3) Add and start MAS instances for accounts on the new list.
      * Called at:
      *  - Each change in accounts
      *  - Each disconnect - before MasInstances restart.
-     *
-     * @return true is any changes are made, false otherwise.
      */
-    private boolean updateMasInstancesHandler(){
-        if (DEBUG) Log.d(TAG,"updateMasInstancesHandler() state = " + getState());
-        boolean changed = false;
-
-        if(getState() == BluetoothMap.STATE_DISCONNECTED) {
-            ArrayList<BluetoothMapAccountItem> newAccountList =
-                    mAppObserver.getEnabledAccountItems();
-            ArrayList<BluetoothMapAccountItem> newAccounts = null;
-            ArrayList<BluetoothMapAccountItem> removedAccounts = null;
-            newAccounts = new ArrayList<BluetoothMapAccountItem>();
-            removedAccounts = mEnabledAccounts; // reuse the current enabled list, to track removed
-                                                // accounts
-            for(BluetoothMapAccountItem account: newAccountList) {
-                if(!removedAccounts.remove(account)) {
-                    newAccounts.add(account);
-                }
-            }
-
-            if(removedAccounts != null) {
-                /* Remove all disabled/removed accounts */
-                for(BluetoothMapAccountItem account : removedAccounts) {
-                    BluetoothMapMasInstance masInst = mMasInstanceMap.remove(account);
-                    if (VERBOSE) Log.v(TAG,"  Removing account: " + account + " masInst = " + masInst);
-                    if(masInst != null) {
-                        masInst.shutdown();
-                        mMasInstances.remove(masInst.getMasId());
-                        changed = true;
-                    }
-                }
-            }
-
-            if(newAccounts != null) {
-                /* Add any newly created accounts */
-                for(BluetoothMapAccountItem account : newAccounts) {
-                    if (VERBOSE) Log.v(TAG,"  Adding account: " + account);
-                    int masId = getNextMasId();
-                    BluetoothMapMasInstance newInst =
-                            new BluetoothMapMasInstance(this,
-                                    this,
-                                    account,
-                                    masId,
-                                    false);
-                    mMasInstances.append(masId, newInst);
-                    mMasInstanceMap.put(account, newInst);
-                    changed = true;
-                    /* Start the new instance */
-                    if (mAdapter.isEnabled()) {
-                        newInst.startRfcommSocketListener();
-                    }
-                }
-            }
-            mEnabledAccounts = newAccountList;
-            if (VERBOSE) {
-                Log.v(TAG,"  Enabled accounts:");
-                for(BluetoothMapAccountItem account : mEnabledAccounts) {
-                    Log.v(TAG, "   " + account);
-                }
-                Log.v(TAG,"  Active MAS instances:");
-                for(int i=0, c=mMasInstances.size(); i < c; i++) {
-                    BluetoothMapMasInstance masInst = mMasInstances.valueAt(i);
-                    Log.v(TAG, "   " + masInst);
-                }
-            }
-            mAccountChanged = false;
-        } else {
-            mAccountChanged = true;
+    private void updateMasInstancesHandler() {
+        if (DEBUG) {
+            Log.d(TAG, "updateMasInstancesHandler() state = " + getState());
         }
-        return changed;
+
+        if (getState() != BluetoothMap.STATE_DISCONNECTED) {
+            mAccountChanged = true;
+            return;
+        }
+
+        ArrayList<BluetoothMapAccountItem> newAccountList = mAppObserver.getEnabledAccountItems();
+        ArrayList<BluetoothMapAccountItem> newAccounts = new ArrayList<>();
+
+        for (BluetoothMapAccountItem account : newAccountList) {
+            if (!mEnabledAccounts.remove(account)) {
+                newAccounts.add(account);
+            }
+        }
+
+        // Remove all disabled/removed accounts
+        if (mEnabledAccounts.size() > 0) {
+            for (BluetoothMapAccountItem account : mEnabledAccounts) {
+                BluetoothMapMasInstance masInst = mMasInstanceMap.remove(account);
+                if (VERBOSE) {
+                    Log.v(TAG, "  Removing account: " + account + " masInst = " + masInst);
+                }
+                if (masInst != null) {
+                    masInst.shutdown();
+                    mMasInstances.remove(masInst.getMasId());
+                }
+            }
+        }
+
+        // Add any newly created accounts
+        for (BluetoothMapAccountItem account : newAccounts) {
+            if (VERBOSE) {
+                Log.v(TAG, "  Adding account: " + account);
+            }
+            int masId = getNextMasId();
+            BluetoothMapMasInstance newInst =
+                    new BluetoothMapMasInstance(this, this, account, masId, false);
+            mMasInstances.append(masId, newInst);
+            mMasInstanceMap.put(account, newInst);
+            // Start the new instance
+            if (mAdapter.isEnabled()) {
+                newInst.startSocketListeners();
+            }
+        }
+
+        mEnabledAccounts = newAccountList;
+        if (VERBOSE) {
+            Log.v(TAG, "  Enabled accounts:");
+            for (BluetoothMapAccountItem account : mEnabledAccounts) {
+                Log.v(TAG, "   " + account);
+            }
+            Log.v(TAG, "  Active MAS instances:");
+            for (int i = 0, c = mMasInstances.size(); i < c; i++) {
+                BluetoothMapMasInstance masInst = mMasInstances.valueAt(i);
+                Log.v(TAG, "   " + masInst);
+            }
+        }
+        mAccountChanged = false;
     }
 
     /**
-     * Will return the next MasId to use.
-     * Will ensure the key returned is greater than the largest key in use.
-     * Unless the key 255 is in use, in which case the first free masId
-     * will be returned.
-     * @return
+     * Return a free key greater than the largest key in use.
+     * If the key 255 is in use, the first free masId will be returned.
+     * @return a free MasId
      */
     private int getNextMasId() {
-        /* Find the largest masId in use */
+        // Find the largest masId in use
         int largestMasId = 0;
-        for(int i=0, c=mMasInstances.size(); i < c; i++) {
+        for (int i = 0, c = mMasInstances.size(); i < c; i++) {
             int masId = mMasInstances.keyAt(i);
-            if(masId > largestMasId) {
+            if (masId > largestMasId) {
                 largestMasId = masId;
             }
         }
-        if(largestMasId < 0xff) {
+        if (largestMasId < 0xff) {
             return largestMasId + 1;
         }
-        /* If 0xff is already in use, wrap and choose the first free
-         * MasId. */
-        for(int i = 1; i <= 0xff; i++) {
-            if(mMasInstances.get(i) == null) {
+        // If 0xff is already in use, wrap and choose the first free MasId.
+        for (int i = 1; i <= 0xff; i++) {
+            if (mMasInstances.get(i) == null) {
                 return i;
             }
         }
@@ -760,67 +777,45 @@
     }
 
     private void createMasInstances() {
-        int masId = mSmsCapable ? MAS_ID_SMS_MMS : -1;
+        int masId = MAS_ID_SMS_MMS;
 
         if (mSmsCapable) {
             // Add the SMS/MMS instance
             BluetoothMapMasInstance smsMmsInst =
-                    new BluetoothMapMasInstance(this,
-                            this,
-                            null,
-                            masId,
-                            true);
+                    new BluetoothMapMasInstance(this, this, null, masId, true);
             mMasInstances.append(masId, smsMmsInst);
             mMasInstanceMap.put(null, smsMmsInst);
+            masId++;
         }
 
         // get list of accounts already set to be visible through MAP
-        for(BluetoothMapAccountItem account : mEnabledAccounts) {
-            masId++;  // SMS/MMS is masId=0, increment before adding next
+        for (BluetoothMapAccountItem account : mEnabledAccounts) {
             BluetoothMapMasInstance newInst =
-                    new BluetoothMapMasInstance(this,
-                            this,
-                            account,
-                            masId,
-                            false);
+                    new BluetoothMapMasInstance(this, this, account, masId, false);
             mMasInstances.append(masId, newInst);
             mMasInstanceMap.put(account, newInst);
+            masId++;
         }
     }
 
     @Override
     protected boolean stop() {
-        if (DEBUG) Log.d(TAG, "stop()");
-        if (mRegisteredMapReceiver) {
-            try {
-                mRegisteredMapReceiver = false;
-                unregisterReceiver(mMapReceiver);
-                mAppObserver.shutdown();
-            } catch (Exception e) {
-                Log.e(TAG,"Unable to unregister map receiver",e);
+        if (DEBUG) {
+            Log.d(TAG, "stop()");
+        }
+        if (!mServiceStarted) {
+            if (DEBUG) {
+                Log.d(TAG, "mServiceStarted is false - Ignoring");
             }
-        }
-        //Stop MapProfile if already started.
-        //TODO: Check if the profile state can be retreived from ProfileService or AdapterService.
-        if (!isMapStarted()) {
-            if (DEBUG) Log.d(TAG, "Service Not Available to STOP, ignoring");
             return true;
-        } else {
-            if (VERBOSE) Log.d(TAG, "Service Stoping()");
         }
-        if (mSessionStatusHandler != null) {
-            sendShutdownMessage();
+        setBluetoothMapService(null);
+        mServiceStarted = false;
+        if (mRegisteredMapReceiver) {
+            mRegisteredMapReceiver = false;
+            unregisterReceiver(mMapReceiver);
+            mAppObserver.shutdown();
         }
-        mStartError = true;
-        setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
-        return true;
-    }
-
-    public boolean cleanup()  {
-        if (DEBUG) Log.d(TAG, "cleanup()");
-        setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED);
-        //Cleanup already handled in Stop().
-        //Move this  extra check to Handler.
         sendShutdownMessage();
         return true;
     }
@@ -829,38 +824,37 @@
      * Called from each MAS instance when a connection is received.
      * @param remoteDevice The device connecting
      * @param masInst a reference to the calling MAS instance.
-     * @return
+     * @return true if the connection was accepted, false otherwise
      */
     public boolean onConnect(BluetoothDevice remoteDevice, BluetoothMapMasInstance masInst) {
         boolean sendIntent = false;
         boolean cancelConnection = false;
 
         // As this can be called from each MasInstance, we need to lock access to member variables
-        synchronized(this) {
-            if (mRemoteDevice == null) {
-                mRemoteDevice = remoteDevice;
-                sRemoteDeviceName = mRemoteDevice.getName();
+        synchronized (this) {
+            if (sRemoteDevice == null) {
+                sRemoteDevice = remoteDevice;
+                sRemoteDeviceName = sRemoteDevice.getName();
                 // In case getRemoteName failed and return null
                 if (TextUtils.isEmpty(sRemoteDeviceName)) {
                     sRemoteDeviceName = getString(R.string.defaultname);
                 }
 
-                mPermission = mRemoteDevice.getMessageAccessPermission();
+                mPermission = sRemoteDevice.getMessageAccessPermission();
                 if (mPermission == BluetoothDevice.ACCESS_UNKNOWN) {
                     sendIntent = true;
                     mIsWaitingAuthorization = true;
                     setUserTimeoutAlarm();
                 } else if (mPermission == BluetoothDevice.ACCESS_REJECTED) {
                     cancelConnection = true;
-                } else if(mPermission == BluetoothDevice.ACCESS_ALLOWED) {
-                    mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
+                } else if (mPermission == BluetoothDevice.ACCESS_ALLOWED) {
+                    sRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
                     mSdpSearchInitiated = true;
                 }
-            } else if (!mRemoteDevice.equals(remoteDevice)) {
-                Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " +
-                            ((remoteDevice==null)?"unknown":remoteDevice.getName()));
-                return false; /* The connecting device is different from what is already
-                                 connected, reject the connection. */
+            } else if (!sRemoteDevice.equals(remoteDevice)) {
+                Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " + (
+                        (remoteDevice == null) ? "unknown" : remoteDevice.getName()));
+                return false;
             } // Else second connection to same device, just continue
         }
 
@@ -869,39 +863,43 @@
             Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
             intent.setPackage(getString(R.string.pairing_ui_package));
             intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-                            BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
-            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+                    BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
+            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, sRemoteDevice);
             sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
 
-            if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
-                    + sRemoteDeviceName);
+            if (VERBOSE) {
+                Log.v(TAG, "waiting for authorization for connection from: " + sRemoteDeviceName);
+            }
             //Queue USER_TIMEOUT to disconnect MAP OBEX session. If user doesn't
             //accept or reject authorization request
         } else if (cancelConnection) {
             sendConnectCancelMessage();
         } else if (mPermission == BluetoothDevice.ACCESS_ALLOWED) {
-            /* Signal to the service that we have a incoming connection. */
+            // Signal to the service that we have a incoming connection.
             sendConnectMessage(masInst.getMasId());
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.MAP);
         }
         return true;
-    };
-
-
-    private void setUserTimeoutAlarm(){
-        if (DEBUG) Log.d(TAG,"SetUserTimeOutAlarm()");
-        if(mAlarmManager == null){
-            mAlarmManager =(AlarmManager) this.getSystemService (Context.ALARM_SERVICE);
-        }
-        mRemoveTimeoutMsg = true;
-        Intent timeoutIntent =
-                new Intent(USER_CONFIRM_TIMEOUT_ACTION);
-        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
-        mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() +
-                USER_CONFIRM_TIMEOUT_VALUE,pIntent);
     }
 
-    private void cancelUserTimeoutAlarm(){
-        if (DEBUG) Log.d(TAG,"cancelUserTimeOutAlarm()");
+    private void setUserTimeoutAlarm() {
+        if (DEBUG) {
+            Log.d(TAG, "SetUserTimeOutAlarm()");
+        }
+        if (mAlarmManager == null) {
+            mAlarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
+        }
+        mRemoveTimeoutMsg = true;
+        Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
+        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
+        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                System.currentTimeMillis() + USER_CONFIRM_TIMEOUT_VALUE, pIntent);
+    }
+
+    private void cancelUserTimeoutAlarm() {
+        if (DEBUG) {
+            Log.d(TAG, "cancelUserTimeOutAlarm()");
+        }
         Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
         PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
         pIntent.cancel();
@@ -915,21 +913,22 @@
      * Start the incoming connection listeners for a MAS ID
      * @param masId the MasID to start. Use -1 to start all listeners.
      */
-    public void sendStartListenerMessage(int masId) {
-        if (mSessionStatusHandler != null && ! mSessionStatusHandler.hasMessages(START_LISTENER)) {
+    void sendStartListenerMessage(int masId) {
+        if (mSessionStatusHandler != null && !mSessionStatusHandler.hasMessages(START_LISTENER)) {
             Message msg = mSessionStatusHandler.obtainMessage(START_LISTENER, masId, 0);
             /* We add a small delay here to ensure the call returns true before this message is
              * handled. It seems wrong to add a delay, but the alternative is to build a lock
              * system to handle synchronization, which isn't nice either... */
             mSessionStatusHandler.sendMessageDelayed(msg, 20);
         } else if (mSessionStatusHandler != null) {
-            if(DEBUG)
+            if (DEBUG) {
                 Log.w(TAG, "mSessionStatusHandler START_LISTENER message already in Queue");
+            }
         }
     }
 
     private void sendConnectMessage(int masId) {
-        if(mSessionStatusHandler != null) {
+        if (mSessionStatusHandler != null) {
             Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT, masId, 0);
             /* We add a small delay here to ensure onConnect returns true before this message is
              * handled. It seems wrong, but the alternative is to store a reference to the
@@ -937,47 +936,48 @@
             mSessionStatusHandler.sendMessageDelayed(msg, 20);
         } // Can only be null during shutdown
     }
+
     private void sendConnectTimeoutMessage() {
-        if (DEBUG) Log.d(TAG, "sendConnectTimeoutMessage()");
-        if(mSessionStatusHandler != null) {
+        if (DEBUG) {
+            Log.d(TAG, "sendConnectTimeoutMessage()");
+        }
+        if (mSessionStatusHandler != null) {
             Message msg = mSessionStatusHandler.obtainMessage(USER_TIMEOUT);
             msg.sendToTarget();
         } // Can only be null during shutdown
     }
+
     private void sendConnectCancelMessage() {
-        if(mSessionStatusHandler != null) {
+        if (mSessionStatusHandler != null) {
             Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT_CANCEL);
             msg.sendToTarget();
         } // Can only be null during shutdown
     }
 
     private void sendShutdownMessage() {
-        /* Any pending messages are no longer valid.
-        To speed up things, simply delete them. */
+        // Pending messages are no longer valid. To speed up things, simply delete them.
         if (mRemoveTimeoutMsg) {
-            Intent timeoutIntent =
-                    new Intent(USER_CONFIRM_TIMEOUT_ACTION);
+            Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
             sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
             mIsWaitingAuthorization = false;
             cancelUserTimeoutAlarm();
         }
-        if (mSessionStatusHandler != null && !mSessionStatusHandler.hasMessages(SHUTDOWN)) {
-            mSessionStatusHandler.removeCallbacksAndMessages(null);
-            // Request release of all resources
-            Message msg = mSessionStatusHandler.obtainMessage(SHUTDOWN);
-            if( mSessionStatusHandler.sendMessage(msg) == false) {
-                /* most likely caused by shutdown being called from multiple sources - e.g.BT off
-                 * signaled through intent and a service shutdown simultaneously.
-                 * Intended behavior not documented, hence we need to be able to handle all cases*/
-            } else {
-                if(DEBUG)
-                    Log.e(TAG, "mSessionStatusHandler.sendMessage() dispatched shutdown message");
-            }
-        } else if (mSessionStatusHandler != null) {
-                if(DEBUG)
-                    Log.w(TAG, "mSessionStatusHandler shutdown message already in Queue");
+        if (mSessionStatusHandler == null) {
+            Log.w(TAG, "mSessionStatusHandler is null");
+            return;
         }
-        if (VERBOSE) Log.d(TAG, "sendShutdownMessage() Out");
+        if (mSessionStatusHandler.hasMessages(SHUTDOWN)) {
+            if (DEBUG) {
+                Log.w(TAG, "mSessionStatusHandler shutdown message already in Queue");
+            }
+            return;
+        }
+        mSessionStatusHandler.removeCallbacksAndMessages(null);
+        // Request release of all resources
+        Message msg = mSessionStatusHandler.obtainMessage(SHUTDOWN);
+        if (!mSessionStatusHandler.sendMessage(msg)) {
+            Log.w(TAG, "mSessionStatusHandler shutdown message could not be sent");
+        }
     }
 
     private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();
@@ -985,36 +985,26 @@
     private class MapBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (DEBUG) Log.d(TAG, "onReceive");
             String action = intent.getAction();
-            if (DEBUG) Log.d(TAG, "onReceive: " + action);
-            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
-                                               BluetoothAdapter.ERROR);
-                if (state == BluetoothAdapter.STATE_TURNING_OFF) {
-                    if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF");
-                    sendShutdownMessage();
-                } else if (state == BluetoothAdapter.STATE_ON) {
-                    if (DEBUG) Log.d(TAG, "STATE_ON");
-                    // start ServerSocket listener threads
-                    sendStartListenerMessage(-1);
+            if (DEBUG) {
+                Log.d(TAG, "onReceive: " + action);
+            }
+            if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)) {
+                if (DEBUG) {
+                    Log.d(TAG, "USER_CONFIRM_TIMEOUT ACTION Received.");
                 }
-
-            }else if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)){
-                if (DEBUG) Log.d(TAG, "USER_CONFIRM_TIMEOUT ACTION Received.");
-                // send us self a message about the timeout.
                 sendConnectTimeoutMessage();
-
             } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
 
                 int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-                                               BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
+                        BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
 
-                if (DEBUG) Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" +
-                           requestType + "isWaitingAuthorization:" + mIsWaitingAuthorization);
-                if ((!mIsWaitingAuthorization)
-                        || (requestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) {
-                    // this reply is not for us
+                if (DEBUG) {
+                    Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" + requestType
+                            + "isWaitingAuthorization:" + mIsWaitingAuthorization);
+                }
+                if ((!mIsWaitingAuthorization) || (requestType
+                        != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) {
                     return;
                 }
 
@@ -1026,42 +1016,44 @@
                 }
 
                 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
-                                       BluetoothDevice.CONNECTION_ACCESS_NO)
+                        BluetoothDevice.CONNECTION_ACCESS_NO)
                         == BluetoothDevice.CONNECTION_ACCESS_YES) {
                     // Bluetooth connection accepted by user
                     mPermission = BluetoothDevice.ACCESS_ALLOWED;
                     if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
-                        boolean result = mRemoteDevice.setMessageAccessPermission(
+                        boolean result = sRemoteDevice.setMessageAccessPermission(
                                 BluetoothDevice.ACCESS_ALLOWED);
                         if (DEBUG) {
-                            Log.d(TAG, "setMessageAccessPermission(ACCESS_ALLOWED) result="
-                                    + result);
+                            Log.d(TAG,
+                                    "setMessageAccessPermission(ACCESS_ALLOWED) result=" + result);
                         }
                     }
 
-                    mRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
+                    sRemoteDevice.sdpSearch(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS);
                     mSdpSearchInitiated = true;
                 } else {
                     // Auth. declined by user, serverSession should not be running, but
                     // call stop anyway to restart listener.
                     mPermission = BluetoothDevice.ACCESS_REJECTED;
                     if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
-                        boolean result = mRemoteDevice.setMessageAccessPermission(
+                        boolean result = sRemoteDevice.setMessageAccessPermission(
                                 BluetoothDevice.ACCESS_REJECTED);
                         if (DEBUG) {
-                            Log.d(TAG, "setMessageAccessPermission(ACCESS_REJECTED) result="
-                                    + result);
+                            Log.d(TAG,
+                                    "setMessageAccessPermission(ACCESS_REJECTED) result=" + result);
                         }
                     }
                     sendConnectCancelMessage();
                 }
             } else if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
-                if (DEBUG) Log.d(TAG, "Received ACTION_SDP_RECORD.");
+                if (DEBUG) {
+                    Log.d(TAG, "Received ACTION_SDP_RECORD.");
+                }
                 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
                 if (VERBOSE) {
                     Log.v(TAG, "Received UUID: " + uuid.toString());
-                    Log.v(TAG, "expected UUID: " +
-                          BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS.toString());
+                    Log.v(TAG, "expected UUID: "
+                            + BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS.toString());
                 }
                 if (uuid.equals(BluetoothMnsObexClient.BLUETOOTH_UUID_OBEX_MNS)) {
                     mMnsRecord = intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
@@ -1075,8 +1067,8 @@
                     }
                     if (status != -1 && mMnsRecord != null) {
                         for (int i = 0, c = mMasInstances.size(); i < c; i++) {
-                                mMasInstances.valueAt(i).setRemoteFeatureMask(
-                                        mMnsRecord.getSupportedFeatures());
+                            mMasInstances.valueAt(i)
+                                    .setRemoteFeatureMask(mMnsRecord.getSupportedFeatures());
                         }
                     }
                     if (mSdpSearchInitiated) {
@@ -1085,149 +1077,201 @@
                     }
                 }
             } else if (action.equals(ACTION_SHOW_MAPS_SETTINGS)) {
-                if (VERBOSE) Log.v(TAG, "Received ACTION_SHOW_MAPS_SETTINGS.");
+                if (VERBOSE) {
+                    Log.v(TAG, "Received ACTION_SHOW_MAPS_SETTINGS.");
+                }
 
                 Intent in = new Intent(context, BluetoothMapSettings.class);
                 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                 context.startActivity(in);
             } else if (action.equals(BluetoothMapContentObserver.ACTION_MESSAGE_SENT)) {
-                BluetoothMapMasInstance masInst = null;
                 int result = getResultCode();
                 boolean handled = false;
-                if(mSmsCapable && mMasInstances != null &&
-                        (masInst = mMasInstances.get(MAS_ID_SMS_MMS)) != null) {
-                    intent.putExtra(BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT, result);
-                    if(masInst.handleSmsSendIntent(context, intent)) {
-                        // The intent was handled by the mas instance it self
-                        handled = true;
+                if (mSmsCapable && mMasInstances != null) {
+                    BluetoothMapMasInstance masInst = mMasInstances.get(MAS_ID_SMS_MMS);
+                    if (masInst != null) {
+                        intent.putExtra(BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT,
+                                result);
+                        handled = masInst.handleSmsSendIntent(context, intent);
                     }
                 }
-                if(handled == false)
-                {
-                    /* We do not have a connection to a device, hence we need to move
-                       the SMS to the correct folder. */
-                    BluetoothMapContentObserver
-                            .actionMessageSentDisconnected(context, intent, result);
+                if (!handled) {
+                    // Move the SMS to the correct folder.
+                    BluetoothMapContentObserver.actionMessageSentDisconnected(context, intent,
+                            result);
                 }
-            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) &&
-                    mIsWaitingAuthorization) {
+            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)
+                    && mIsWaitingAuthorization) {
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 
-                if (mRemoteDevice == null || device == null) {
+                if (sRemoteDevice == null || device == null) {
                     Log.e(TAG, "Unexpected error!");
                     return;
                 }
 
-                if (VERBOSE) Log.v(TAG,"ACL disconnected for " + device);
+                if (VERBOSE) {
+                    Log.v(TAG, "ACL disconnected for " + device);
+                }
 
-                if (mRemoteDevice.equals(device)) {
+                if (sRemoteDevice.equals(device)) {
                     // Send any pending timeout now, since ACL got disconnected
                     mSessionStatusHandler.removeMessages(USER_TIMEOUT);
                     mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
                 }
             }
         }
-    };
+    }
 
     //Binder object: Must be static class or memory leak may occur
+
     /**
      * This class implements the IBluetoothMap interface - or actually it validates the
      * preconditions for calling the actual functionality in the MapService, and calls it.
      */
     private static class BluetoothMapBinder extends IBluetoothMap.Stub
-        implements IProfileServiceBinder {
+            implements IProfileServiceBinder {
         private BluetoothMapService mService;
 
         private BluetoothMapService getService() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"MAP call not allowed for non-active user");
+                Log.w(TAG, "MAP call not allowed for non-active user");
                 return null;
             }
 
             if (mService != null && mService.isAvailable()) {
-                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,"Need BLUETOOTH permission");
+                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                        "Need BLUETOOTH permission");
                 return mService;
             }
             return null;
         }
 
         BluetoothMapBinder(BluetoothMapService service) {
-            if (VERBOSE) Log.v(TAG, "BluetoothMapBinder()");
+            if (VERBOSE) {
+                Log.v(TAG, "BluetoothMapBinder()");
+            }
             mService = service;
         }
 
-        public boolean cleanup()  {
+        @Override
+        public synchronized void cleanup() {
             mService = null;
-            return true;
         }
 
+        @Override
         public int getState() {
-            if (VERBOSE) Log.v(TAG, "getState()");
+            if (VERBOSE) {
+                Log.v(TAG, "getState()");
+            }
             BluetoothMapService service = getService();
-            if (service == null) return BluetoothMap.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothMap.STATE_DISCONNECTED;
+            }
             return getService().getState();
         }
 
+        @Override
         public BluetoothDevice getClient() {
-            if (VERBOSE) Log.v(TAG, "getClient()");
+            if (VERBOSE) {
+                Log.v(TAG, "getClient()");
+            }
             BluetoothMapService service = getService();
-            if (service == null) return null;
-            if (VERBOSE) Log.v(TAG, "getClient() - returning " + service.getRemoteDevice());
-            return service.getRemoteDevice();
+            if (service == null) {
+                return null;
+            }
+            BluetoothDevice client = BluetoothMapService.getRemoteDevice();
+            if (VERBOSE) {
+                Log.v(TAG, "getClient() - returning " + client);
+            }
+            return client;
         }
 
+        @Override
         public boolean isConnected(BluetoothDevice device) {
-            if (VERBOSE) Log.v(TAG, "isConnected()");
+            if (VERBOSE) {
+                Log.v(TAG, "isConnected()");
+            }
             BluetoothMapService service = getService();
-            if (service == null) return false;
-            return service.getState() == BluetoothMap.STATE_CONNECTED
-                    && service.getRemoteDevice().equals(device);
+            return service != null && service.getState() == BluetoothMap.STATE_CONNECTED
+                    && BluetoothMapService.getRemoteDevice().equals(device);
         }
 
+        @Override
         public boolean connect(BluetoothDevice device) {
-            if (VERBOSE) Log.v(TAG, "connect()");
+            if (VERBOSE) {
+                Log.v(TAG, "connect()");
+            }
             BluetoothMapService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return false;
         }
 
+        @Override
         public boolean disconnect(BluetoothDevice device) {
-            if (VERBOSE) Log.v(TAG, "disconnect()");
+            if (VERBOSE) {
+                Log.v(TAG, "disconnect()");
+            }
             BluetoothMapService service = getService();
-            if (service == null) return false;
-            return service.disconnect(device);
+            if (service == null) {
+                return false;
+            }
+            service.disconnect(device);
+            return true;
         }
 
+        @Override
         public List<BluetoothDevice> getConnectedDevices() {
-            if (VERBOSE) Log.v(TAG, "getConnectedDevices()");
+            if (VERBOSE) {
+                Log.v(TAG, "getConnectedDevices()");
+            }
             BluetoothMapService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<>(0);
+            }
             return service.getConnectedDevices();
         }
 
+        @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            if (VERBOSE) Log.v(TAG, "getDevicesMatchingConnectionStates()");
+            if (VERBOSE) {
+                Log.v(TAG, "getDevicesMatchingConnectionStates()");
+            }
             BluetoothMapService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<>(0);
+            }
             return service.getDevicesMatchingConnectionStates(states);
         }
 
+        @Override
         public int getConnectionState(BluetoothDevice device) {
-            if (VERBOSE) Log.v(TAG, "getConnectionState()");
+            if (VERBOSE) {
+                Log.v(TAG, "getConnectionState()");
+            }
             BluetoothMapService service = getService();
-            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
             return service.getConnectionState(device);
         }
 
+        @Override
         public boolean setPriority(BluetoothDevice device, int priority) {
             BluetoothMapService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPriority(device, priority);
         }
 
+        @Override
         public int getPriority(BluetoothDevice device) {
             BluetoothMapService service = getService();
-            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
+            if (service == null) {
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
             return service.getPriority(device);
         }
     }
@@ -1235,7 +1279,7 @@
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
-        println(sb, "mRemoteDevice: " + mRemoteDevice);
+        println(sb, "mRemoteDevice: " + sRemoteDevice);
         println(sb, "sRemoteDeviceName: " + sRemoteDeviceName);
         println(sb, "mState: " + mState);
         println(sb, "mAppObserver: " + mAppObserver);
diff --git a/src/com/android/bluetooth/map/BluetoothMapSettings.java b/src/com/android/bluetooth/map/BluetoothMapSettings.java
index 101acb1..94535c2 100644
--- a/src/com/android/bluetooth/map/BluetoothMapSettings.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSettings.java
@@ -15,16 +15,15 @@
 
 package com.android.bluetooth.map;
 
-import com.android.bluetooth.R;
-import com.android.bluetooth.map.BluetoothMapAccountItem;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-
 import android.app.Activity;
 import android.os.Bundle;
 import android.widget.ExpandableListView;
 
+import com.android.bluetooth.R;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+
 
 public class BluetoothMapSettings extends Activity {
 
@@ -33,9 +32,8 @@
     private static final boolean V = BluetoothMapService.VERBOSE;
 
 
-
     BluetoothMapAccountLoader mLoader = new BluetoothMapAccountLoader(this);
-    LinkedHashMap<BluetoothMapAccountItem,ArrayList<BluetoothMapAccountItem>> mGroups;
+    LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mGroups;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -48,10 +46,11 @@
 
         /* update expandable listview with correct items */
         ExpandableListView listView =
-            (ExpandableListView) findViewById(R.id.bluetooth_map_settings_list_view);
+                (ExpandableListView) findViewById(R.id.bluetooth_map_settings_list_view);
 
-        BluetoothMapSettingsAdapter adapter = new BluetoothMapSettingsAdapter(this,
-                listView, mGroups, mLoader.getAccountsEnabledCount());
+        BluetoothMapSettingsAdapter adapter =
+                new BluetoothMapSettingsAdapter(this, listView, mGroups,
+                        mLoader.getAccountsEnabledCount());
         listView.setAdapter(adapter);
     }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java b/src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java
index bc7db8c..2bdee07 100644
--- a/src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSettingsAdapter.java
@@ -15,10 +15,6 @@
 
 package com.android.bluetooth.map;
 
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -30,19 +26,21 @@
 import android.view.ViewGroup;
 import android.widget.BaseExpandableListAdapter;
 import android.widget.CheckBox;
+import android.widget.CompoundButton;
 import android.widget.CompoundButton.OnCheckedChangeListener;
 import android.widget.ExpandableListView;
 import android.widget.ExpandableListView.OnGroupExpandListener;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
-import android.widget.CompoundButton;
 
 import com.android.bluetooth.R;
-
-import com.android.bluetooth.map.BluetoothMapAccountItem;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
 
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 public class BluetoothMapSettingsAdapter extends BaseExpandableListAdapter {
     private static final boolean D = BluetoothMapService.DEBUG;
     private static final boolean V = BluetoothMapService.VERBOSE;
@@ -50,39 +48,39 @@
     private boolean mCheckAll = true;
     public LayoutInflater mInflater;
     public Activity mActivity;
-    /*needed to prevent random checkbox toggles due to item reuse */
-    ArrayList<Boolean> mPositionArray;
-    private LinkedHashMap<BluetoothMapAccountItem,
-                            ArrayList<BluetoothMapAccountItem>> mProupList;
+    /*needed to prevent random checkbox toggles due to item reuse */ ArrayList<Boolean>
+            mPositionArray;
+    private LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mProupList;
     private ArrayList<BluetoothMapAccountItem> mMainGroup;
     private int[] mGroupStatus;
     /* number of accounts possible to share */
     private int mSlotsLeft = 10;
 
 
-    public BluetoothMapSettingsAdapter(Activity act,
-                                            ExpandableListView listView,
-                                            LinkedHashMap<BluetoothMapAccountItem,
-                                              ArrayList<BluetoothMapAccountItem>> groupsList,
-                                            int enabledAccountsCounts) {
+    public BluetoothMapSettingsAdapter(Activity act, ExpandableListView listView,
+            LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> groupsList,
+            int enabledAccountsCounts) {
         mActivity = act;
         this.mProupList = groupsList;
         mInflater = act.getLayoutInflater();
         mGroupStatus = new int[groupsList.size()];
-        mSlotsLeft = mSlotsLeft-enabledAccountsCounts;
+        mSlotsLeft = mSlotsLeft - enabledAccountsCounts;
 
         listView.setOnGroupExpandListener(new OnGroupExpandListener() {
 
+            @Override
             public void onGroupExpand(int groupPosition) {
                 BluetoothMapAccountItem group = mMainGroup.get(groupPosition);
-                if (mProupList.get(group).size() > 0)
+                if (mProupList.get(group).size() > 0) {
                     mGroupStatus[groupPosition] = 1;
+                }
 
             }
         });
         mMainGroup = new ArrayList<BluetoothMapAccountItem>();
-        for (Map.Entry<BluetoothMapAccountItem, 
-                ArrayList<BluetoothMapAccountItem>> mapEntry : mProupList.entrySet()) {
+        for (Map.Entry<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mapEntry :
+                mProupList
+                .entrySet()) {
             mMainGroup.add(mapEntry.getKey());
         }
     }
@@ -92,6 +90,7 @@
         BluetoothMapAccountItem item = mMainGroup.get(groupPosition);
         return mProupList.get(item).get(childPosition);
     }
+
     private ArrayList<BluetoothMapAccountItem> getChild(BluetoothMapAccountItem group) {
         return mProupList.get(group);
     }
@@ -102,8 +101,8 @@
     }
 
     @Override
-    public View getChildView(final int groupPosition, final int childPosition,
-            boolean isLastChild, View convertView, ViewGroup parent) {
+    public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild,
+            View convertView, ViewGroup parent) {
 
 
         final ChildHolder holder;
@@ -111,85 +110,86 @@
             convertView = mInflater.inflate(R.layout.bluetooth_map_settings_account_item, null);
             holder = new ChildHolder();
             holder.cb = (CheckBox) convertView.findViewById(R.id.bluetooth_map_settings_item_check);
-            holder.title = 
-                (TextView) convertView.findViewById(R.id.bluetooth_map_settings_item_text_view);
+            holder.title =
+                    (TextView) convertView.findViewById(R.id.bluetooth_map_settings_item_text_view);
             convertView.setTag(holder);
         } else {
             holder = (ChildHolder) convertView.getTag();
         }
-            final BluetoothMapAccountItem child =  getChild(groupPosition, childPosition);
-            holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+        final BluetoothMapAccountItem child = getChild(groupPosition, childPosition);
+        holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
 
-                public void onCheckedChanged(CompoundButton buttonView,
-                        boolean isChecked) {
-                    BluetoothMapAccountItem parentGroup = 
-                          (BluetoothMapAccountItem)getGroup(groupPosition);
-                    boolean oldIsChecked = child.mIsChecked; // needed to prevent updates on UI redraw
-                    child.mIsChecked = isChecked;
-                    if (isChecked) {
-                        ArrayList<BluetoothMapAccountItem> childList = getChild(parentGroup);
-                        int childIndex = childList.indexOf(child);
-                        boolean isAllChildClicked = true;
-                        if(mSlotsLeft-childList.size() >=0){
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                BluetoothMapAccountItem parentGroup =
+                        (BluetoothMapAccountItem) getGroup(groupPosition);
+                boolean oldIsChecked = child.mIsChecked; // needed to prevent updates on UI redraw
+                child.mIsChecked = isChecked;
+                if (isChecked) {
+                    ArrayList<BluetoothMapAccountItem> childList = getChild(parentGroup);
+                    int childIndex = childList.indexOf(child);
+                    boolean isAllChildClicked = true;
+                    if (mSlotsLeft - childList.size() >= 0) {
 
-                            for (int i = 0; i < childList.size(); i++) {
-                                if (i != childIndex) {
-                                    BluetoothMapAccountItem siblings = childList.get(i);
-                                    if (!siblings.mIsChecked) {
-                                        isAllChildClicked = false;
-                                            BluetoothMapSettingsDataHolder.mCheckedChilds.put(
-                                                child.getName(), parentGroup.getName());
-                                        break;
+                        for (int i = 0; i < childList.size(); i++) {
+                            if (i != childIndex) {
+                                BluetoothMapAccountItem siblings = childList.get(i);
+                                if (!siblings.mIsChecked) {
+                                    isAllChildClicked = false;
+                                    BluetoothMapSettingsDataHolder.sCheckedChilds.put(
+                                            child.getName(), parentGroup.getName());
+                                    break;
 
-                                    }
                                 }
                             }
-                        }else {
-                            showWarning(mActivity.getString(
-                                R.string.bluetooth_map_settings_no_account_slots_left));
-                            isAllChildClicked = false;
-                            child.mIsChecked = false;
                         }
-                        if (isAllChildClicked) {
-                            parentGroup.mIsChecked = true;
-                            if(!(BluetoothMapSettingsDataHolder.mCheckedChilds.containsKey(
-                                child.getName())==true)){
-                                BluetoothMapSettingsDataHolder.mCheckedChilds.put(child.getName(),
-                                        parentGroup.getName());
-                            }
-                            mCheckAll = false;
-                        }
-
-
                     } else {
-                        if (parentGroup.mIsChecked) {
-                            parentGroup.mIsChecked = false;
-                            mCheckAll = false;
-                            BluetoothMapSettingsDataHolder.mCheckedChilds.remove(child.getName());
-                        } else {
-                            mCheckAll = true;
-                            BluetoothMapSettingsDataHolder.mCheckedChilds.remove(child.getName());
-                        }
-                        // child.isChecked =false;
+                        showWarning(mActivity.getString(
+                                R.string.bluetooth_map_settings_no_account_slots_left));
+                        isAllChildClicked = false;
+                        child.mIsChecked = false;
                     }
-                    notifyDataSetChanged();
-                    if(child.mIsChecked != oldIsChecked){
-                        updateAccount(child);
+                    if (isAllChildClicked) {
+                        parentGroup.mIsChecked = true;
+                        if (!(BluetoothMapSettingsDataHolder.sCheckedChilds.containsKey(
+                                child.getName()))) {
+                            BluetoothMapSettingsDataHolder.sCheckedChilds.put(child.getName(),
+                                    parentGroup.getName());
+                        }
+                        mCheckAll = false;
                     }
 
+
+                } else {
+                    if (parentGroup.mIsChecked) {
+                        parentGroup.mIsChecked = false;
+                        mCheckAll = false;
+                        BluetoothMapSettingsDataHolder.sCheckedChilds.remove(child.getName());
+                    } else {
+                        mCheckAll = true;
+                        BluetoothMapSettingsDataHolder.sCheckedChilds.remove(child.getName());
+                    }
+                    // child.isChecked =false;
+                }
+                notifyDataSetChanged();
+                if (child.mIsChecked != oldIsChecked) {
+                    updateAccount(child);
                 }
 
-            });
+            }
 
-            holder.cb.setChecked(child.mIsChecked);
-            holder.title.setText(child.getName());
-            if(D)Log.i("childs are", BluetoothMapSettingsDataHolder.mCheckedChilds.toString());
-            return convertView;
+        });
+
+        holder.cb.setChecked(child.mIsChecked);
+        holder.title.setText(child.getName());
+        if (D) {
+            Log.i("childs are", BluetoothMapSettingsDataHolder.sCheckedChilds.toString());
+        }
+        return convertView;
 
     }
 
 
-
     @Override
     public int getChildrenCount(int groupPosition) {
         BluetoothMapAccountItem item = mMainGroup.get(groupPosition);
@@ -222,20 +222,20 @@
     }
 
     @Override
-    public View getGroupView(int groupPosition, boolean isExpanded,
-            View convertView, ViewGroup parent) {
+    public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+            ViewGroup parent) {
 
         final GroupHolder holder;
 
         if (convertView == null) {
             convertView = mInflater.inflate(R.layout.bluetooth_map_settings_account_group, null);
             holder = new GroupHolder();
-            holder.cb = 
-                (CheckBox) convertView.findViewById(R.id.bluetooth_map_settings_group_checkbox);
-            holder.imageView = (ImageView) convertView
-                    .findViewById(R.id.bluetooth_map_settings_group_icon);
-            holder.title = 
-                (TextView) convertView.findViewById(R.id.bluetooth_map_settings_group_text_view);
+            holder.cb =
+                    (CheckBox) convertView.findViewById(R.id.bluetooth_map_settings_group_checkbox);
+            holder.imageView =
+                    (ImageView) convertView.findViewById(R.id.bluetooth_map_settings_group_icon);
+            holder.title = (TextView) convertView.findViewById(
+                    R.id.bluetooth_map_settings_group_text_view);
             convertView.setTag(holder);
         } else {
             holder = (GroupHolder) convertView.getTag();
@@ -249,19 +249,18 @@
 
         holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
 
-            public void onCheckedChanged(CompoundButton buttonView,
-                    boolean isChecked) {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 if (mCheckAll) {
                     ArrayList<BluetoothMapAccountItem> childItem = getChild(groupItem);
-                    for (BluetoothMapAccountItem children : childItem)
-                    {
+                    for (BluetoothMapAccountItem children : childItem) {
                         boolean oldIsChecked = children.mIsChecked;
-                        if(mSlotsLeft >0){
+                        if (mSlotsLeft > 0) {
                             children.mIsChecked = isChecked;
-                            if(oldIsChecked != children.mIsChecked){
+                            if (oldIsChecked != children.mIsChecked) {
                                 updateAccount(children);
                             }
-                        }else {
+                        } else {
                             showWarning(mActivity.getString(
                                     R.string.bluetooth_map_settings_no_account_slots_left));
                             isChecked = false;
@@ -272,9 +271,11 @@
                 notifyDataSetChanged();
                 new Handler().postDelayed(new Runnable() {
 
+                    @Override
                     public void run() {
-                        if (!mCheckAll)
+                        if (!mCheckAll) {
                             mCheckAll = true;
+                        }
                     }
                 }, 50);
 
@@ -307,33 +308,36 @@
         public TextView title;
         public CheckBox cb;
     }
+
     public void updateAccount(BluetoothMapAccountItem account) {
         updateSlotCounter(account.mIsChecked);
-        if(D)Log.d(TAG,"Updating account settings for "
-                +account.getName() +". Value is:"+account.mIsChecked);
+        if (D) {
+            Log.d(TAG, "Updating account settings for " + account.getName() + ". Value is:"
+                    + account.mIsChecked);
+        }
         ContentResolver mResolver = mActivity.getContentResolver();
-        Uri uri = Uri.parse(account.mBase_uri_no_account+"/"+BluetoothMapContract.TABLE_ACCOUNT);
+        Uri uri =
+                Uri.parse(account.mBase_uri_no_account + "/" + BluetoothMapContract.TABLE_ACCOUNT);
         ContentValues values = new ContentValues();
-        values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, ((account.mIsChecked)?1:0)); 
+        values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, ((account.mIsChecked) ? 1 : 0));
         values.put(BluetoothMapContract.AccountColumns._ID, account.getId()); // get title
-        mResolver.update(uri, values, null ,null);
+        mResolver.update(uri, values, null, null);
 
     }
-    private void updateSlotCounter(boolean isChecked){
-        if(isChecked)
-        {
+
+    private void updateSlotCounter(boolean isChecked) {
+        if (isChecked) {
             mSlotsLeft--;
-        }else {
+        } else {
             mSlotsLeft++;
         }
         CharSequence text;
 
-        if (mSlotsLeft <=0)
-        {
+        if (mSlotsLeft <= 0) {
             text = mActivity.getString(R.string.bluetooth_map_settings_no_account_slots_left);
-        }else {
-            text= mActivity.getString(R.string.bluetooth_map_settings_count) 
-                + " "+ String.valueOf(mSlotsLeft);
+        } else {
+            text = mActivity.getString(R.string.bluetooth_map_settings_count) + " "
+                    + String.valueOf(mSlotsLeft);
         }
 
         int duration = Toast.LENGTH_SHORT;
@@ -341,7 +345,8 @@
         Toast toast = Toast.makeText(mActivity, text, duration);
         toast.show();
     }
-    private void showWarning(String text){
+
+    private void showWarning(String text) {
         int duration = Toast.LENGTH_SHORT;
 
         Toast toast = Toast.makeText(mActivity, text, duration);
diff --git a/src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java b/src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java
index e33d705..f256b1c 100644
--- a/src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSettingsDataHolder.java
@@ -18,6 +18,6 @@
 import java.util.HashMap;
 
 public class BluetoothMapSettingsDataHolder {
-        public static HashMap<String, String> mCheckedChilds = new HashMap<String, String>();
+    public static HashMap<String, String> sCheckedChilds = new HashMap<String, String>();
 }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
index d14e7e4..f6af8db 100644
--- a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
@@ -15,17 +15,6 @@
 package com.android.bluetooth.map;
 
 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
-import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Random;
 
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SmsMessage;
@@ -38,29 +27,43 @@
 import com.android.internal.telephony.cdma.sms.BearerData;
 import com.android.internal.telephony.cdma.sms.UserData;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Random;
+
 public class BluetoothMapSmsPdu {
 
     private static final String TAG = "BluetoothMapSmsPdu";
     private static final boolean V = false;
-    private static int INVALID_VALUE = -1;
-    public static int SMS_TYPE_GSM = 1;
-    public static int SMS_TYPE_CDMA = 2;
+    private static final int INVALID_VALUE = -1;
+    public static final int SMS_TYPE_GSM = 1;
+    public static final int SMS_TYPE_CDMA = 2;
 
 
     /* We need to handle the SC-address mentioned in errata 4335.
      * Since the definition could be read in three different ways, I have asked
      * the car working group for clarification, and are awaiting confirmation that
      * this clarification will go into the MAP spec:
-     *  "The native format should be <sc_addr><tpdu> where <sc_addr> is <length><ton><1..10 octet of address>
-     *   coded according to 24.011. The IEI is not to be used, as the fixed order of the data makes a type 4 LV
-     *   information element sufficient. <length> is a single octet which value is the length of the value-field
+     *  "The native format should be <sc_addr><tpdu> where <sc_addr> is <length><ton><1..10 octet
+      *  of address>
+     *   coded according to 24.011. The IEI is not to be used, as the fixed order of the data
+     *   makes a type 4 LV
+     *   information element sufficient. <length> is a single octet which value is the length of
+     *   the value-field
      *   in octets including both the <ton> and the <address>."
      * */
 
 
     public static class SmsPdu {
         private byte[] mData;
-        private byte[] mScAddress = {0}; // At the moment we do not use the scAddress, hence set the length to 0.
+        private byte[] mScAddress = {0};
+        // At the moment we do not use the scAddress, hence set the length to 0.
         private int mUserDataMsgOffset = 0;
         private int mEncoding;
         private int mLanguageTable;
@@ -71,7 +74,7 @@
         private int mUserDataSeptetPadding = INVALID_VALUE;
         private int mMsgSeptetCount = 0;
 
-        SmsPdu(byte[] data, int type){
+        SmsPdu(byte[] data, int type) {
             this.mData = data;
             this.mEncoding = INVALID_VALUE;
             this.mType = type;
@@ -87,30 +90,37 @@
          * @param type
          * @param languageTable
          */
-        SmsPdu(byte[]data, int encoding, int type, int languageTable){
+        SmsPdu(byte[] data, int encoding, int type, int languageTable) {
             this.mData = data;
             this.mEncoding = encoding;
             this.mType = type;
             this.mLanguageTable = languageTable;
         }
-        public byte[] getData(){
+
+        public byte[] getData() {
             return mData;
         }
-        public byte[] getScAddress(){
+
+        public byte[] getScAddress() {
             return mScAddress;
         }
+
         public void setEncoding(int encoding) {
             this.mEncoding = encoding;
         }
-        public int getEncoding(){
+
+        public int getEncoding() {
             return mEncoding;
         }
-        public int getType(){
+
+        public int getType() {
             return mType;
         }
+
         public int getUserDataMsgOffset() {
             return mUserDataMsgOffset;
         }
+
         /** The user data message payload size in bytes - excluding the user data header. */
         public int getUserDataMsgSize() {
             return mData.length - mUserDataMsgOffset;
@@ -134,15 +144,15 @@
 
 
         /* PDU parsing/modification functionality */
-        private final static byte TELESERVICE_IDENTIFIER                    = 0x00;
-        private final static byte SERVICE_CATEGORY                          = 0x01;
-        private final static byte ORIGINATING_ADDRESS                       = 0x02;
-        private final static byte ORIGINATING_SUB_ADDRESS                   = 0x03;
-        private final static byte DESTINATION_ADDRESS                       = 0x04;
-        private final static byte DESTINATION_SUB_ADDRESS                   = 0x05;
-        private final static byte BEARER_REPLY_OPTION                       = 0x06;
-        private final static byte CAUSE_CODES                               = 0x07;
-        private final static byte BEARER_DATA                               = 0x08;
+        private static final byte TELESERVICE_IDENTIFIER = 0x00;
+        private static final byte SERVICE_CATEGORY = 0x01;
+        private static final byte ORIGINATING_ADDRESS = 0x02;
+        private static final byte ORIGINATING_SUB_ADDRESS = 0x03;
+        private static final byte DESTINATION_ADDRESS = 0x04;
+        private static final byte DESTINATION_SUB_ADDRESS = 0x05;
+        private static final byte BEARER_REPLY_OPTION = 0x06;
+        private static final byte CAUSE_CODES = 0x07;
+        private static final byte BEARER_DATA = 0x08;
 
         /**
          * Find and return the offset to the specified parameter ID
@@ -163,11 +173,10 @@
                     int currentId = pdu.read();
                     int currentLen = pdu.read();
 
-                    if(currentId == parameterId) {
+                    if (currentId == parameterId) {
                         found = true;
                         break;
-                    }
-                    else {
+                    } else {
                         pdu.skip(currentLen);
                         offset += 2 + currentLen;
                     }
@@ -177,19 +186,21 @@
                 Log.e(TAG, "cdmaGetParameterOffset: ", e);
             }
 
-            if(found)
+            if (found) {
                 return offset;
-            else
+            } else {
                 return 0;
+            }
         }
 
-        private final static byte BEARER_DATA_MSG_ID = 0x00;
+        private static final byte BEARER_DATA_MSG_ID = 0x00;
 
         private int cdmaGetSubParameterOffset(byte subParameterId) {
             ByteArrayInputStream pdu = new ByteArrayInputStream(mData);
             int offset = 0;
             boolean found = false;
-            offset = cdmaGetParameterOffset(BEARER_DATA) + 2; // Add to offset the BEARER_DATA parameter id and length bytes
+            offset = cdmaGetParameterOffset(BEARER_DATA)
+                    + 2; // Add to offset the BEARER_DATA parameter id and length bytes
             pdu.skip(offset);
             try {
 
@@ -197,11 +208,10 @@
                     int currentId = pdu.read();
                     int currentLen = pdu.read();
 
-                    if(currentId == subParameterId) {
+                    if (currentId == subParameterId) {
                         found = true;
                         break;
-                    }
-                    else {
+                    } else {
                         pdu.skip(currentLen);
                         offset += 2 + currentLen;
                     }
@@ -211,68 +221,77 @@
                 Log.e(TAG, "cdmaGetParameterOffset: ", e);
             }
 
-            if(found)
+            if (found) {
                 return offset;
-            else
+            } else {
                 return 0;
+            }
         }
 
 
-        public void cdmaChangeToDeliverPdu(long date){
+        public void cdmaChangeToDeliverPdu(long date) {
             /* Things to change:
              *  - Message Type in bearer data (Not the overall point-to-point type)
              *  - Change address ID from destination to originating (sub addresses are not used)
              *  - A time stamp is not mandatory.
              */
             int offset;
-            if(mData == null) {
+            if (mData == null) {
                 throw new IllegalArgumentException("Unable to convert PDU to Deliver type");
             }
             offset = cdmaGetParameterOffset(DESTINATION_ADDRESS);
-            if(mData.length < offset) {
+            if (mData.length < offset) {
                 throw new IllegalArgumentException("Unable to convert PDU to Deliver type");
             }
             mData[offset] = ORIGINATING_ADDRESS;
 
             offset = cdmaGetParameterOffset(DESTINATION_SUB_ADDRESS);
-            if(mData.length < offset) {
+            if (mData.length < offset) {
                 throw new IllegalArgumentException("Unable to convert PDU to Deliver type");
             }
             mData[offset] = ORIGINATING_SUB_ADDRESS;
 
             offset = cdmaGetSubParameterOffset(BEARER_DATA_MSG_ID);
 
-            if(mData.length > (2+offset)) {
-                int tmp = mData[offset+2] & 0xff; // Skip the subParam ID and length, and read the first byte.
+            if (mData.length > (2 + offset)) {
+                int tmp = mData[offset + 2]
+                        & 0xff; // Skip the subParam ID and length, and read the first byte.
                 // Mask out the type
                 tmp &= 0x0f;
                 // Set the new type
                 tmp |= ((BearerData.MESSAGE_TYPE_DELIVER << 4) & 0xf0);
                 // Store the result
-                mData[offset+2] = (byte) tmp;
+                mData[offset + 2] = (byte) tmp;
 
             } else {
                 throw new IllegalArgumentException("Unable to convert PDU to Deliver type");
             }
-                /* TODO: Do we need to change anything in the user data? Not sure if the user data is
-                 *        just encoded using GSM encoding, or it is an actual GSM submit PDU embedded
+                /* TODO: Do we need to change anything in the user data? Not sure if the user
+                data is
+                 *        just encoded using GSM encoding, or it is an actual GSM submit PDU
+                 *        embedded
                  *        in the user data?
                  */
         }
 
-        private static final byte TP_MIT_DELIVER       = 0x00; // bit 0 and 1
-        private static final byte TP_MMS_NO_MORE       = 0x04; // bit 2
-        private static final byte TP_RP_NO_REPLY_PATH  = 0x00; // bit 7
-        private static final byte TP_UDHI_MASK         = 0x40; // bit 6
-        private static final byte TP_SRI_NO_REPORT     = 0x00; // bit 5
+        private static final byte TP_MIT_DELIVER = 0x00; // bit 0 and 1
+        private static final byte TP_MMS_NO_MORE = 0x04; // bit 2
+        private static final byte TP_RP_NO_REPLY_PATH = 0x00; // bit 7
+        private static final byte TP_UDHI_MASK = 0x40; // bit 6
+        private static final byte TP_SRI_NO_REPORT = 0x00; // bit 5
 
         private int gsmSubmitGetTpPidOffset() {
             /* calculate the offset to TP_PID.
-             * The TP-DA has variable length, and the length excludes the 2 byte length and type headers.
+             * The TP-DA has variable length, and the length excludes the 2 byte length and type
+             * headers.
              * The TP-DA is two bytes within the PDU */
-            int offset = 2 + ((mData[2]+1) & 0xff)/2 + 2; // data[2] is the number of semi-octets in the phone number (ceil result)
-            if((offset > mData.length) || (offset > (2 + 12))) // max length of TP_DA is 12 bytes + two byte offset.
-                throw new IllegalArgumentException("wrongly formatted gsm submit PDU. offset = " + offset);
+            // data[2] is the number of semi-octets in the phone number (ceil result)
+            int offset = 2 + ((mData[2] + 1) & 0xff) / 2 + 2;
+            // max length of TP_DA is 12 bytes + two byte offset.
+            if ((offset > mData.length) || (offset > (2 + 12))) {
+                throw new IllegalArgumentException(
+                        "wrongly formatted gsm submit PDU. offset = " + offset);
+            }
             return offset;
         }
 
@@ -289,17 +308,18 @@
         }
 
         private int gsmSubmitGetTpUdlOffset() {
-            switch(((mData[0]  & 0xff) & (0x08 | 0x04))>>2) {
-            case 0: // Not TP-VP present
-                return gsmSubmitGetTpPidOffset() + 2;
-            case 1: // TP-VP relative format
-                return gsmSubmitGetTpPidOffset() + 2 + 1;
-            case 2: // TP-VP enhanced format
-            case 3: // TP-VP absolute format
-                break;
+            switch (((mData[0] & 0xff) & (0x08 | 0x04)) >> 2) {
+                case 0: // Not TP-VP present
+                    return gsmSubmitGetTpPidOffset() + 2;
+                case 1: // TP-VP relative format
+                    return gsmSubmitGetTpPidOffset() + 2 + 1;
+                case 2: // TP-VP enhanced format
+                case 3: // TP-VP absolute format
+                    break;
             }
             return gsmSubmitGetTpPidOffset() + 2 + 7;
         }
+
         private int gsmSubmitGetTpUdOffset() {
             return gsmSubmitGetTpUdlOffset() + 1;
         }
@@ -309,12 +329,12 @@
 
             pdu.skip(gsmSubmitGetTpUdlOffset());
             int userDataLength = pdu.read();
-            if(gsmSubmitHasUserDataHeader() == true) {
+            if (gsmSubmitHasUserDataHeader()) {
                 int userDataHeaderLength = pdu.read();
 
-                // This part is only needed to extract the language info, hence only needed for 7 bit encoding
-                if(mEncoding == SmsConstants.ENCODING_7BIT)
-                {
+                // This part is only needed to extract the language info, hence only needed for 7
+                // bit encoding
+                if (mEncoding == SmsConstants.ENCODING_7BIT) {
                     byte[] udh = new byte[userDataHeaderLength];
                     try {
                         pdu.read(udh);
@@ -331,15 +351,14 @@
                     mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
                     mMsgSeptetCount = userDataLength - headerSeptets;
                 }
-                mUserDataMsgOffset = gsmSubmitGetTpUdOffset() + userDataHeaderLength + 1; // Add the byte containing the length
-            }
-            else
-            {
+                mUserDataMsgOffset = gsmSubmitGetTpUdOffset() + userDataHeaderLength
+                        + 1; // Add the byte containing the length
+            } else {
                 mUserDataSeptetPadding = 0;
                 mMsgSeptetCount = userDataLength;
                 mUserDataMsgOffset = gsmSubmitGetTpUdOffset();
             }
-            if(V) {
+            if (V) {
                 Log.v(TAG, "encoding:" + mEncoding);
                 Log.v(TAG, "msgSeptetCount:" + mMsgSeptetCount);
                 Log.v(TAG, "userDataSeptetPadding:" + mUserDataSeptetPadding);
@@ -349,29 +368,33 @@
             }
         }
 
-        private void gsmWriteDate(ByteArrayOutputStream header, long time) throws UnsupportedEncodingException {
+        private void gsmWriteDate(ByteArrayOutputStream header, long time)
+                throws UnsupportedEncodingException {
             SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss");
             Date date = new Date(time);
             String timeStr = format.format(date); // Format to YYMMDDTHHMMSS UTC time
-            if(V) Log.v(TAG, "Generated time string: " + timeStr);
+            if (V) {
+                Log.v(TAG, "Generated time string: " + timeStr);
+            }
             byte[] timeChars = timeStr.getBytes("US-ASCII");
 
-            for(int i = 0, n = timeStr.length(); i < n; i+=2) {
-                header.write((timeChars[i+1]-0x30) << 4 | (timeChars[i]-0x30)); // Offset from ascii char to decimal value
+            for (int i = 0, n = timeStr.length(); i < n; i += 2) {
+                header.write((timeChars[i + 1] - 0x30) << 4 | (timeChars[i]
+                        - 0x30)); // Offset from ascii char to decimal value
             }
 
             Calendar cal = Calendar.getInstance();
-            int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (15 * 60 * 1000); /* offset in quarters of an hour */
+            int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (15 * 60
+                    * 1000); /* offset in quarters of an hour */
             String offsetString;
-            if(offset < 0) {
+            if (offset < 0) {
                 offsetString = String.format("%1$02d", -(offset));
                 char[] offsetChars = offsetString.toCharArray();
-                header.write((offsetChars[1]-0x30) << 4 | 0x40 | (offsetChars[0]-0x30));
-            }
-            else {
+                header.write((offsetChars[1] - 0x30) << 4 | 0x40 | (offsetChars[0] - 0x30));
+            } else {
                 offsetString = String.format("%1$02d", offset);
                 char[] offsetChars = offsetString.toCharArray();
-                header.write((offsetChars[1]-0x30) << 4 | (offsetChars[0]-0x30));
+                header.write((offsetChars[1] - 0x30) << 4 | (offsetChars[0] - 0x30));
             }
         }
 
@@ -388,22 +411,28 @@
          *  - Extract encoding details from the submit PDU
          *  - Extract user data length and user data from the submitPdu
          *  - Build the new PDU
-         * @param date the time stamp to include (The value is the number of milliseconds since Jan. 1, 1970 GMT.)
-         * @param originator the phone number to include in the deliver PDU header. Any undesired characters,
+         * @param date the time stamp to include (The value is the number of milliseconds since
+         * Jan. 1, 1970 GMT.)
+         * @param originator the phone number to include in the deliver PDU header. Any undesired
+         * characters,
          *                    such as '-' will be striped from this string.
          */
-        public void gsmChangeToDeliverPdu(long date, String originator)
-        {
-            ByteArrayOutputStream newPdu = new ByteArrayOutputStream(22); // 22 is the max length of the deliver pdu header
+        public void gsmChangeToDeliverPdu(long date, String originator) {
+            ByteArrayOutputStream newPdu =
+                    new ByteArrayOutputStream(22); // 22 is the max length of the deliver pdu header
             byte[] encodedAddress;
             int userDataLength = 0;
             try {
-                newPdu.write(TP_MIT_DELIVER | TP_MMS_NO_MORE | TP_RP_NO_REPLY_PATH | TP_SRI_NO_REPORT
-                             | (mData[0] & 0xff)  & TP_UDHI_MASK);
-                encodedAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(originator);
-                if(encodedAddress != null) {
-                    int padding = (encodedAddress[encodedAddress.length-1] & 0xf0) == 0xf0 ? 1 : 0;
-                    encodedAddress[0] = (byte)((encodedAddress[0]-1)*2 - padding); // Convert from octet length to semi octet length
+                newPdu.write(
+                        TP_MIT_DELIVER | TP_MMS_NO_MORE | TP_RP_NO_REPLY_PATH | TP_SRI_NO_REPORT
+                                | (mData[0] & 0xff) & TP_UDHI_MASK);
+                encodedAddress =
+                        PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(originator);
+                if (encodedAddress != null) {
+                    int padding =
+                            (encodedAddress[encodedAddress.length - 1] & 0xf0) == 0xf0 ? 1 : 0;
+                    encodedAddress[0] = (byte) ((encodedAddress[0] - 1) * 2
+                            - padding); // Convert from octet length to semi octet length
                     // Insert originator address into the header - this includes the length
                     newPdu.write(encodedAddress);
                 } else {
@@ -417,8 +446,10 @@
                 gsmWriteDate(newPdu, date);
                 userDataLength = (mData[gsmSubmitGetTpUdlOffset()] & 0xff);
                 newPdu.write(userDataLength);
-                // Copy the pdu user data - keep in mind that the userDataLength is not the length in bytes for 7-bit encoding.
-                newPdu.write(mData, gsmSubmitGetTpUdOffset(), mData.length - gsmSubmitGetTpUdOffset());
+                // Copy the pdu user data - keep in mind that the userDataLength is not the
+                // length in bytes for 7-bit encoding.
+                newPdu.write(mData, gsmSubmitGetTpUdOffset(),
+                        mData.length - gsmSubmitGetTpUdOffset());
             } catch (IOException e) {
                 Log.e(TAG, "", e);
                 throw new IllegalArgumentException("Failed to change type to deliver PDU.");
@@ -427,37 +458,38 @@
         }
 
         /* SMS encoding to bmessage strings */
+
         /** get the encoding type as a bMessage string */
-        public String getEncodingString(){
-            if(mType == SMS_TYPE_GSM)
-            {
-                switch(mEncoding){
-                case SmsMessage.ENCODING_7BIT:
-                    if(mLanguageTable == 0)
-                        return "G-7BIT";
-                    else
-                        return "G-7BITEXT";
-                case SmsMessage.ENCODING_8BIT:
-                    return "G-8BIT";
-                case SmsMessage.ENCODING_16BIT:
-                    return "G-16BIT";
-                case SmsMessage.ENCODING_UNKNOWN:
+        public String getEncodingString() {
+            if (mType == SMS_TYPE_GSM) {
+                switch (mEncoding) {
+                    case SmsMessage.ENCODING_7BIT:
+                        if (mLanguageTable == 0) {
+                            return "G-7BIT";
+                        } else {
+                            return "G-7BITEXT";
+                        }
+                    case SmsMessage.ENCODING_8BIT:
+                        return "G-8BIT";
+                    case SmsMessage.ENCODING_16BIT:
+                        return "G-16BIT";
+                    case SmsMessage.ENCODING_UNKNOWN:
                     default:
-                    return "";
+                        return "";
                 }
             } else /* SMS_TYPE_CDMA */ {
-                switch(mEncoding){
-                case SmsMessage.ENCODING_7BIT:
-                    return "C-7ASCII";
-                case SmsMessage.ENCODING_8BIT:
-                    return "C-8BIT";
-                case SmsMessage.ENCODING_16BIT:
-                    return "C-UNICODE";
-                case SmsMessage.ENCODING_KSC5601:
-                    return "C-KOREAN";
-                case SmsMessage.ENCODING_UNKNOWN:
+                switch (mEncoding) {
+                    case SmsMessage.ENCODING_7BIT:
+                        return "C-7ASCII";
+                    case SmsMessage.ENCODING_8BIT:
+                        return "C-8BIT";
+                    case SmsMessage.ENCODING_16BIT:
+                        return "C-UNICODE";
+                    case SmsMessage.ENCODING_KSC5601:
+                        return "C-KOREAN";
+                    case SmsMessage.ENCODING_UNKNOWN:
                     default:
-                    return "";
+                        return "";
                 }
             }
         }
@@ -469,16 +501,21 @@
         sConcatenatedRef += 1;
         return sConcatenatedRef;
     }
-    public static ArrayList<SmsPdu> getSubmitPdus(String messageText, String address){
+
+    public static ArrayList<SmsPdu> getSubmitPdus(String messageText, String address) {
         /* Use the generic GSM/CDMA SMS Message functionality within Android to generate the
          * SMS PDU's as once generated to send the SMS message.
          */
 
-        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); // TODO: Change to use: ((TelephonyManager)myContext.getSystemService(Context.TELEPHONY_SERVICE))
+        int activePhone = TelephonyManager.getDefault()
+                .getCurrentPhoneType(); // TODO: Change to use: ((TelephonyManager)myContext
+        // .getSystemService(Context.TELEPHONY_SERVICE))
         int phoneType;
-        GsmAlphabet.TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ?
-            com.android.internal.telephony.cdma.SmsMessage.calculateLength((CharSequence)messageText, false, true) :
-            com.android.internal.telephony.gsm.SmsMessage.calculateLength((CharSequence)messageText, false);
+        GsmAlphabet.TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone)
+                ? com.android.internal.telephony.cdma.SmsMessage.calculateLength(
+                (CharSequence) messageText, false, true)
+                : com.android.internal.telephony.gsm.SmsMessage.calculateLength(
+                        (CharSequence) messageText, false);
 
         SmsPdu newPdu;
         String destinationAddress;
@@ -497,20 +534,20 @@
         languageTable = ted.languageTable;
         languageShiftTable = ted.languageShiftTable;
         destinationAddress = PhoneNumberUtils.stripSeparators(address);
-        if(destinationAddress == null || destinationAddress.length() < 2) {
-            destinationAddress = "12"; // Ensure we add a number at least 2 digits as specified in the GSM spec.
+        if (destinationAddress == null || destinationAddress.length() < 2) {
+            destinationAddress =
+                    "12"; // Ensure we add a number at least 2 digits as specified in the GSM spec.
         }
 
-        if(msgCount == 1){
-            data = SmsMessage.getSubmitPdu(null, destinationAddress, smsFragments.get(0), false).encodedMessage;
+        if (msgCount == 1) {
+            data = SmsMessage.getSubmitPdu(null, destinationAddress, smsFragments.get(0),
+                    false).encodedMessage;
             newPdu = new SmsPdu(data, encoding, phoneType, languageTable);
             pdus.add(newPdu);
-        }
-        else
-        {
+        } else {
             /* This code is a reduced copy of the actual code used in the Android SMS sub system,
              * hence the comments have been left untouched. */
-            for(int i = 0; i < msgCount; i++){
+            for (int i = 0; i < msgCount; i++) {
                 SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
                 concatRef.refNumber = refNumber;
                 concatRef.seqNumber = i + 1;  // 1-based sequence
@@ -535,10 +572,11 @@
                     smsHeader.languageShiftTable = languageShiftTable;
                 }
 
-                if(phoneType == SMS_TYPE_GSM){
-                    data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, destinationAddress,
-                            smsFragments.get(i), false, SmsHeader.toByteArray(smsHeader),
-                            encoding, languageTable, languageShiftTable).encodedMessage;
+                if (phoneType == SMS_TYPE_GSM) {
+                    data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
+                            destinationAddress, smsFragments.get(i), false,
+                            SmsHeader.toByteArray(smsHeader), encoding, languageTable,
+                            languageShiftTable).encodedMessage;
                 } else { // SMS_TYPE_CDMA
                     UserData uData = new UserData();
                     uData.payloadStr = smsFragments.get(i);
@@ -549,8 +587,8 @@
                         uData.msgEncoding = UserData.ENCODING_UNICODE_16;
                     }
                     uData.msgEncodingSet = true;
-                    data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(destinationAddress,
-                            uData, false).encodedMessage;
+                    data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
+                            destinationAddress, uData, false).encodedMessage;
                 }
                 newPdu = new SmsPdu(data, encoding, phoneType, languageTable);
                 pdus.add(newPdu);
@@ -561,14 +599,15 @@
     }
 
     /**
-     * Generate a list of deliver PDUs. The messageText and address parameters must be different from null,
+     * Generate a list of deliver PDUs. The messageText and address parameters must be different
+     * from null,
      * for CDMA the date can be omitted (and will be ignored if supplied)
      * @param messageText The text to include.
      * @param address The originator address.
      * @param date The delivery time stamp.
      * @return
      */
-    public static ArrayList<SmsPdu> getDeliverPdus(String messageText, String address, long date){
+    public static ArrayList<SmsPdu> getDeliverPdus(String messageText, String address, long date) {
         ArrayList<SmsPdu> deliverPdus = getSubmitPdus(messageText, address);
 
         /*
@@ -578,8 +617,8 @@
          *
          * For GSM, a larger part of the header needs to be generated.
          */
-        for(SmsPdu currentPdu : deliverPdus){
-            if(currentPdu.getType() == SMS_TYPE_CDMA){
+        for (SmsPdu currentPdu : deliverPdus) {
+            if (currentPdu.getType() == SMS_TYPE_CDMA) {
                 currentPdu.cdmaChangeToDeliverPdu(date);
             } else { /* SMS_TYPE_GSM */
                 currentPdu.gsmChangeToDeliverPdu(date, address);
@@ -597,11 +636,13 @@
      */
     public static String decodePdu(byte[] data, int type) {
         String ret;
-        if(type == SMS_TYPE_CDMA) {
+        if (type == SMS_TYPE_CDMA) {
             /* This is able to handle both submit and deliver PDUs */
-            ret = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(0, data).getMessageBody();
+            ret = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(0, data)
+                    .getMessageBody();
         } else {
-            /* For GSM, there is no submit pdu decoder, and most parser utils are private, and only minded for submit pdus */
+            /* For GSM, there is no submit pdu decoder, and most parser utils are private, and
+            only minded for submit pdus */
             ret = gsmParseSubmitPdu(data);
         }
         return ret;
@@ -614,11 +655,14 @@
         /* The format of a native GSM SMS is: <sc-address><pdu> where sc-address is:
          * <length-byte><type-byte><number-bytes> */
         int addressLength = data[0] & 0xff; // Treat the byte value as an unsigned value
-        if(addressLength >= data.length) // We could verify that the address-length is no longer than 11 bytes
-            throw new IllegalArgumentException("Length of address exeeds the length of the PDU data.");
-        int pduLength = data.length-(1+addressLength);
+        // We could verify that the address-length is no longer than 11 bytes
+        if (addressLength >= data.length) {
+            throw new IllegalArgumentException(
+                    "Length of address exeeds the length of the PDU data.");
+        }
+        int pduLength = data.length - (1 + addressLength);
         byte[] newData = new byte[pduLength];
-        System.arraycopy(data, 1+addressLength, newData, 0, pduLength);
+        System.arraycopy(data, 1 + addressLength, newData, 0, pduLength);
         return newData;
     }
 
@@ -633,7 +677,7 @@
         SmsPdu pdu = new SmsPdu(gsmStripOffScAddress(data), SMS_TYPE_GSM);
         boolean userDataCompressed = false;
         int dataCodingScheme = pdu.gsmSubmitGetTpDcs();
-        int encodingType =  SmsConstants.ENCODING_UNKNOWN;
+        int encodingType = SmsConstants.ENCODING_UNKNOWN;
         String messageBody = null;
 
         // Look up the data encoding scheme
@@ -642,24 +686,24 @@
             userDataCompressed = (0 != (dataCodingScheme & 0x20));
 
             if (userDataCompressed) {
-                Log.w(TAG, "4 - Unsupported SMS data coding scheme "
-                        + "(compression) " + (dataCodingScheme & 0xff));
+                Log.w(TAG, "4 - Unsupported SMS data coding scheme " + "(compression) " + (
+                        dataCodingScheme & 0xff));
             } else {
                 switch ((dataCodingScheme >> 2) & 0x3) {
-                case 0: // GSM 7 bit default alphabet
-                    encodingType =  SmsConstants.ENCODING_7BIT;
-                    break;
+                    case 0: // GSM 7 bit default alphabet
+                        encodingType = SmsConstants.ENCODING_7BIT;
+                        break;
 
-                case 2: // UCS 2 (16bit)
-                    encodingType =  SmsConstants.ENCODING_16BIT;
-                    break;
+                    case 2: // UCS 2 (16bit)
+                        encodingType = SmsConstants.ENCODING_16BIT;
+                        break;
 
-                case 1: // 8 bit data
-                case 3: // reserved
-                    Log.w(TAG, "1 - Unsupported SMS data coding scheme "
-                            + (dataCodingScheme & 0xff));
-                    encodingType =  SmsConstants.ENCODING_8BIT;
-                    break;
+                    case 1: // 8 bit data
+                    case 3: // reserved
+                        Log.w(TAG, "1 - Unsupported SMS data coding scheme " + (dataCodingScheme
+                                & 0xff));
+                        encodingType = SmsConstants.ENCODING_8BIT;
+                        break;
                 }
             }
         } else if ((dataCodingScheme & 0xf0) == 0xf0) {
@@ -667,13 +711,12 @@
 
             if (0 == (dataCodingScheme & 0x04)) {
                 // GSM 7 bit default alphabet
-                encodingType =  SmsConstants.ENCODING_7BIT;
+                encodingType = SmsConstants.ENCODING_7BIT;
             } else {
                 // 8 bit data
-                encodingType =  SmsConstants.ENCODING_8BIT;
+                encodingType = SmsConstants.ENCODING_8BIT;
             }
-        } else if ((dataCodingScheme & 0xF0) == 0xC0
-                || (dataCodingScheme & 0xF0) == 0xD0
+        } else if ((dataCodingScheme & 0xF0) == 0xC0 || (dataCodingScheme & 0xF0) == 0xD0
                 || (dataCodingScheme & 0xF0) == 0xE0) {
             // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
 
@@ -682,9 +725,9 @@
             // 0xE0 == UCS-2, store
 
             if ((dataCodingScheme & 0xF0) == 0xE0) {
-                encodingType =  SmsConstants.ENCODING_16BIT;
+                encodingType = SmsConstants.ENCODING_16BIT;
             } else {
-                encodingType =  SmsConstants.ENCODING_7BIT;
+                encodingType = SmsConstants.ENCODING_7BIT;
             }
 
             userDataCompressed = false;
@@ -695,14 +738,12 @@
             // 0x80..0xBF == Reserved coding groups
             if (dataCodingScheme == 0x84) {
                 // This value used for KSC5601 by carriers in Korea.
-                encodingType =  SmsConstants.ENCODING_KSC5601;
+                encodingType = SmsConstants.ENCODING_KSC5601;
             } else {
-                Log.w(TAG, "5 - Unsupported SMS data coding scheme "
-                        + (dataCodingScheme & 0xff));
+                Log.w(TAG, "5 - Unsupported SMS data coding scheme " + (dataCodingScheme & 0xff));
             }
         } else {
-            Log.w(TAG, "3 - Unsupported SMS data coding scheme "
-                    + (dataCodingScheme & 0xff));
+            Log.w(TAG, "3 - Unsupported SMS data coding scheme " + (dataCodingScheme & 0xff));
         }
 
         pdu.setEncoding(encodingType);
@@ -710,29 +751,32 @@
 
         try {
             switch (encodingType) {
-            case  SmsConstants.ENCODING_UNKNOWN:
-            case  SmsConstants.ENCODING_8BIT:
-                Log.w(TAG, "Unknown encoding type: " + encodingType);
-                messageBody = null;
-                break;
+                case SmsConstants.ENCODING_UNKNOWN:
+                case SmsConstants.ENCODING_8BIT:
+                    Log.w(TAG, "Unknown encoding type: " + encodingType);
+                    messageBody = null;
+                    break;
 
-            case  SmsConstants.ENCODING_7BIT:
-                messageBody = GsmAlphabet.gsm7BitPackedToString(pdu.getData(), pdu.getUserDataMsgOffset(),
-                                pdu.getMsgSeptetCount(), pdu.getUserDataSeptetPadding(), pdu.getLanguageTable(),
-                                pdu.getLanguageShiftTable());
-                Log.i(TAG, "Decoded as 7BIT: " + messageBody);
+                case SmsConstants.ENCODING_7BIT:
+                    messageBody = GsmAlphabet.gsm7BitPackedToString(pdu.getData(),
+                            pdu.getUserDataMsgOffset(), pdu.getMsgSeptetCount(),
+                            pdu.getUserDataSeptetPadding(), pdu.getLanguageTable(),
+                            pdu.getLanguageShiftTable());
+                    Log.i(TAG, "Decoded as 7BIT: " + messageBody);
 
-                break;
+                    break;
 
-            case  SmsConstants.ENCODING_16BIT:
-                messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), pdu.getUserDataMsgSize(), "utf-16");
-                Log.i(TAG, "Decoded as 16BIT: " + messageBody);
-                break;
+                case SmsConstants.ENCODING_16BIT:
+                    messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(),
+                            pdu.getUserDataMsgSize(), "utf-16");
+                    Log.i(TAG, "Decoded as 16BIT: " + messageBody);
+                    break;
 
-            case SmsConstants.ENCODING_KSC5601:
-                messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), pdu.getUserDataMsgSize(), "KSC5601");
-                Log.i(TAG, "Decoded as KSC5601: " + messageBody);
-                break;
+                case SmsConstants.ENCODING_KSC5601:
+                    messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(),
+                            pdu.getUserDataMsgSize(), "KSC5601");
+                    Log.i(TAG, "Decoded as KSC5601: " + messageBody);
+                    break;
             }
         } catch (UnsupportedEncodingException e) {
             Log.e(TAG, "Unsupported encoding type???", e); // This should never happen.
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
old mode 100755
new mode 100644
index f10c4ec..e867bd6
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -45,52 +45,52 @@
      *       in stead of a bit to indicate the message type. Then 4
      *       bit can be use for 16 different message types.
      */
-    private static final long HANDLE_TYPE_MASK            = (((long)0xff)<<56);
-    private static final long HANDLE_TYPE_MMS_MASK        = (((long)0x01)<<56);
-    private static final long HANDLE_TYPE_EMAIL_MASK      = (((long)0x02)<<56);
-    private static final long HANDLE_TYPE_SMS_GSM_MASK    = (((long)0x04)<<56);
-    private static final long HANDLE_TYPE_SMS_CDMA_MASK   = (((long)0x08)<<56);
-    private static final long HANDLE_TYPE_IM_MASK         = (((long)0x10)<<56);
+    private static final long HANDLE_TYPE_MASK = (((long) 0xff) << 56);
+    private static final long HANDLE_TYPE_MMS_MASK = (((long) 0x01) << 56);
+    private static final long HANDLE_TYPE_EMAIL_MASK = (((long) 0x02) << 56);
+    private static final long HANDLE_TYPE_SMS_GSM_MASK = (((long) 0x04) << 56);
+    private static final long HANDLE_TYPE_SMS_CDMA_MASK = (((long) 0x08) << 56);
+    private static final long HANDLE_TYPE_IM_MASK = (((long) 0x10) << 56);
 
     public static final long CONVO_ID_TYPE_SMS_MMS = 1;
-    public static final long CONVO_ID_TYPE_EMAIL_IM= 2;
+    public static final long CONVO_ID_TYPE_EMAIL_IM = 2;
 
     // MAP supported feature bit - included from MAP Spec 1.2
-    static final int MAP_FEATURE_DEFAULT_BITMASK                    = 0x0000001F;
+    static final int MAP_FEATURE_DEFAULT_BITMASK = 0x0000001F;
 
-    static final int MAP_FEATURE_NOTIFICATION_REGISTRATION_BIT      = 1 << 0;
-    static final int MAP_FEATURE_NOTIFICATION_BIT                   = 1 << 1;
-    static final int MAP_FEATURE_BROWSING_BIT                       = 1 << 2;
-    static final int MAP_FEATURE_UPLOADING_BIT                      = 1 << 3;
-    static final int MAP_FEATURE_DELETE_BIT                         = 1 << 4;
-    static final int MAP_FEATURE_INSTANCE_INFORMATION_BIT           = 1 << 5;
-    static final int MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT       = 1 << 6;
-    static final int MAP_FEATURE_EVENT_REPORT_V12_BIT               = 1 << 7;
-    static final int MAP_FEATURE_MESSAGE_FORMAT_V11_BIT             = 1 << 8;
-    static final int MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT     = 1 << 9;
-    static final int MAP_FEATURE_PERSISTENT_MESSAGE_HANDLE_BIT      = 1 << 10;
-    static final int MAP_FEATURE_DATABASE_INDENTIFIER_BIT           = 1 << 11;
-    static final int MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT         = 1 << 12;
-    static final int MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT   = 1 << 13;
-    static final int MAP_FEATURE_PARTICIPANT_PRESENCE_CHANGE_BIT    = 1 << 14;
-    static final int MAP_FEATURE_PARTICIPANT_CHAT_STATE_CHANGE_BIT  = 1 << 15;
+    static final int MAP_FEATURE_NOTIFICATION_REGISTRATION_BIT = 1 << 0;
+    static final int MAP_FEATURE_NOTIFICATION_BIT = 1 << 1;
+    static final int MAP_FEATURE_BROWSING_BIT = 1 << 2;
+    static final int MAP_FEATURE_UPLOADING_BIT = 1 << 3;
+    static final int MAP_FEATURE_DELETE_BIT = 1 << 4;
+    static final int MAP_FEATURE_INSTANCE_INFORMATION_BIT = 1 << 5;
+    static final int MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT = 1 << 6;
+    static final int MAP_FEATURE_EVENT_REPORT_V12_BIT = 1 << 7;
+    static final int MAP_FEATURE_MESSAGE_FORMAT_V11_BIT = 1 << 8;
+    static final int MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT = 1 << 9;
+    static final int MAP_FEATURE_PERSISTENT_MESSAGE_HANDLE_BIT = 1 << 10;
+    static final int MAP_FEATURE_DATABASE_INDENTIFIER_BIT = 1 << 11;
+    static final int MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT = 1 << 12;
+    static final int MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT = 1 << 13;
+    static final int MAP_FEATURE_PARTICIPANT_PRESENCE_CHANGE_BIT = 1 << 14;
+    static final int MAP_FEATURE_PARTICIPANT_CHAT_STATE_CHANGE_BIT = 1 << 15;
 
-    static final int MAP_FEATURE_PBAP_CONTACT_CROSS_REFERENCE_BIT   = 1 << 16;
-    static final int MAP_FEATURE_NOTIFICATION_FILTERING_BIT         = 1 << 17;
-    static final int MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT       = 1 << 18;
+    static final int MAP_FEATURE_PBAP_CONTACT_CROSS_REFERENCE_BIT = 1 << 16;
+    static final int MAP_FEATURE_NOTIFICATION_FILTERING_BIT = 1 << 17;
+    static final int MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT = 1 << 18;
 
     static final String MAP_V10_STR = "1.0";
     static final String MAP_V11_STR = "1.1";
     static final String MAP_V12_STR = "1.2";
 
     // Event Report versions
-    static final int MAP_EVENT_REPORT_V10           = 10; // MAP spec 1.1
-    static final int MAP_EVENT_REPORT_V11           = 11; // MAP spec 1.2
-    static final int MAP_EVENT_REPORT_V12           = 12; // MAP spec 1.3 'to be' incl. IM
+    static final int MAP_EVENT_REPORT_V10 = 10; // MAP spec 1.1
+    static final int MAP_EVENT_REPORT_V11 = 11; // MAP spec 1.2
+    static final int MAP_EVENT_REPORT_V12 = 12; // MAP spec 1.3 'to be' incl. IM
 
     // Message Format versions
-    static final int MAP_MESSAGE_FORMAT_V10         = 10; // MAP spec below 1.3
-    static final int MAP_MESSAGE_FORMAT_V11         = 11; // MAP spec 1.3
+    static final int MAP_MESSAGE_FORMAT_V10 = 10; // MAP spec below 1.3
+    static final int MAP_MESSAGE_FORMAT_V11 = 11; // MAP spec 1.3
 
     // Message Listing Format versions
     static final int MAP_MESSAGE_LISTING_FORMAT_V10 = 10; // MAP spec below 1.3
@@ -100,22 +100,19 @@
      * This enum is used to convert from the bMessage type property to a type safe
      * type. Hence do not change the names of the enum values.
      */
-    public enum TYPE{
-        NONE,
-        EMAIL,
-        SMS_GSM,
-        SMS_CDMA,
-        MMS,
-        IM;
-        private static TYPE[] allValues = values();
+    public enum TYPE {
+        NONE, EMAIL, SMS_GSM, SMS_CDMA, MMS, IM;
+        private static TYPE[] sAllValues = values();
+
         public static TYPE fromOrdinal(int n) {
-            if(n < allValues.length)
-               return allValues[n];
+            if (n < sAllValues.length) {
+                return sAllValues[n];
+            }
             return NONE;
         }
     }
 
-    static public String getDateTimeString(long timestamp) {
+    public static String getDateTimeString(long timestamp) {
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
         Date date = new Date(timestamp);
         return format.format(date); // Format to YYYYMMDDTHHMMSS local time
@@ -126,17 +123,25 @@
         if (D) {
             StringBuilder sb = new StringBuilder();
             sb.append("\nprintCursor:\n");
-            for(int i = 0; i < c.getColumnCount(); i++) {
-                if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE) ||
-                   c.getColumnName(i).equals(
-                           BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY) ||
-                   c.getColumnName(i).equals(BluetoothMapContract.ChatStatusColumns.LAST_ACTIVE) ||
-                   c.getColumnName(i).equals(BluetoothMapContract.PresenceColumns.LAST_ONLINE) ){
-                    sb.append("  ").append(c.getColumnName(i)).append(" : ").append(
-                            getDateTimeString(c.getLong(i))).append("\n");
+            for (int i = 0; i < c.getColumnCount(); i++) {
+                if (c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE)
+                        || c.getColumnName(i)
+                        .equals(BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY)
+                        || c.getColumnName(i)
+                        .equals(BluetoothMapContract.ChatStatusColumns.LAST_ACTIVE)
+                        || c.getColumnName(i)
+                        .equals(BluetoothMapContract.PresenceColumns.LAST_ONLINE)) {
+                    sb.append("  ")
+                            .append(c.getColumnName(i))
+                            .append(" : ")
+                            .append(getDateTimeString(c.getLong(i)))
+                            .append("\n");
                 } else {
-                    sb.append("  ").append(c.getColumnName(i)).append(" : ").append(
-                            c.getString(i)).append("\n");
+                    sb.append("  ")
+                            .append(c.getColumnName(i))
+                            .append(" : ")
+                            .append(c.getString(i))
+                            .append("\n");
                 }
             }
             Log.d(TAG, sb.toString());
@@ -146,16 +151,16 @@
     public static String getLongAsString(long v) {
         char[] result = new char[16];
         int v1 = (int) (v & 0xffffffff);
-        int v2 = (int) ((v>>32) & 0xffffffff);
+        int v2 = (int) ((v >> 32) & 0xffffffff);
         int c;
         for (int i = 0; i < 8; i++) {
             c = v2 & 0x0f;
-            c += (c < 10) ? '0' : ('A'-10);
+            c += (c < 10) ? '0' : ('A' - 10);
             result[7 - i] = (char) c;
             v2 >>= 4;
             c = v1 & 0x0f;
-            c += (c < 10) ? '0' : ('A'-10);
-            result[15 - i] = (char)c;
+            c += (c < 10) ? '0' : ('A' - 10);
+            result[15 - i] = (char) c;
             v1 >>= 4;
         }
         return new String(result);
@@ -174,26 +179,34 @@
      *
      */
     public static long getLongFromString(String valueStr) throws UnsupportedEncodingException {
-        if(valueStr == null) throw new NullPointerException();
-        if(V) Log.i(TAG, "getLongFromString(): converting: " + valueStr);
+        if (valueStr == null) {
+            throw new NullPointerException();
+        }
+        if (V) {
+            Log.i(TAG, "getLongFromString(): converting: " + valueStr);
+        }
         byte[] nibbles;
         nibbles = valueStr.getBytes("US-ASCII");
-        if(V) Log.i(TAG, "  byte values: " + Arrays.toString(nibbles));
+        if (V) {
+            Log.i(TAG, "  byte values: " + Arrays.toString(nibbles));
+        }
         byte c;
         int count = 0;
         int length = nibbles.length;
         long value = 0;
-        for(int i = 0; i != length; i++) {
+        for (int i = 0; i != length; i++) {
             c = nibbles[i];
-            if(c >= '0' && c <= '9') {
+            if (c >= '0' && c <= '9') {
                 c -= '0';
-            } else if(c >= 'A' && c <= 'F') {
-                c -= ('A'-10);
-            } else if(c >= 'a' && c <= 'f') {
-                c -= ('a'-10);
-            } else if(c <= ' ' || c == '-') {
-                if(V)Log.v(TAG, "Skipping c = '" + new String(new byte[]{ (byte)c }, "US-ASCII")
-                        + "'");
+            } else if (c >= 'A' && c <= 'F') {
+                c -= ('A' - 10);
+            } else if (c >= 'a' && c <= 'f') {
+                c -= ('a' - 10);
+            } else if (c <= ' ' || c == '-') {
+                if (V) {
+                    Log.v(TAG,
+                            "Skipping c = '" + new String(new byte[]{(byte) c}, "US-ASCII") + "'");
+                }
                 continue; // Skip any whitespace and '-' (which is used for UUIDs)
             } else {
                 throw new NumberFormatException("Invalid character:" + c);
@@ -201,45 +214,51 @@
             value = value << 4; // The last nibble shall not be shifted
             value += c;
             count++;
-            if(count > 16) throw new NullPointerException("String to large - count: " + count);
+            if (count > 16) {
+                throw new NullPointerException("String to large - count: " + count);
+            }
         }
-        if(V) Log.i(TAG, "  length: " + count);
+        if (V) {
+            Log.i(TAG, "  length: " + count);
+        }
         return value;
     }
+
     private static final int LONG_LONG_LENGTH = 32;
+
     public static String getLongLongAsString(long vLow, long vHigh) {
         char[] result = new char[LONG_LONG_LENGTH];
         int v1 = (int) (vLow & 0xffffffff);
-        int v2 = (int) ((vLow>>32) & 0xffffffff);
+        int v2 = (int) ((vLow >> 32) & 0xffffffff);
         int v3 = (int) (vHigh & 0xffffffff);
-        int v4 = (int) ((vHigh>>32) & 0xffffffff);
-        int c,d,i;
+        int v4 = (int) ((vHigh >> 32) & 0xffffffff);
+        int c, d, i;
         // Handle the lower bytes
         for (i = 0; i < 8; i++) {
             c = v2 & 0x0f;
-            c += (c < 10) ? '0' : ('A'-10);
+            c += (c < 10) ? '0' : ('A' - 10);
             d = v4 & 0x0f;
-            d += (d < 10) ? '0' : ('A'-10);
+            d += (d < 10) ? '0' : ('A' - 10);
             result[23 - i] = (char) c;
             result[7 - i] = (char) d;
             v2 >>= 4;
             v4 >>= 4;
             c = v1 & 0x0f;
-            c += (c < 10) ? '0' : ('A'-10);
+            c += (c < 10) ? '0' : ('A' - 10);
             d = v3 & 0x0f;
-            d += (d < 10) ? '0' : ('A'-10);
-            result[31 - i] = (char)c;
-            result[15 - i] = (char)d;
+            d += (d < 10) ? '0' : ('A' - 10);
+            result[31 - i] = (char) c;
+            result[15 - i] = (char) d;
             v1 >>= 4;
             v3 >>= 4;
         }
         // Remove any leading 0's
-        for(i = 0; i < LONG_LONG_LENGTH; i++) {
-            if(result[i] != '0') {
+        for (i = 0; i < LONG_LONG_LENGTH; i++) {
+            if (result[i] != '0') {
                 break;
             }
         }
-        return new String(result, i, LONG_LONG_LENGTH-i);
+        return new String(result, i, LONG_LONG_LENGTH - i);
     }
 
 
@@ -249,12 +268,11 @@
      * @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL)
      * @return String Formatted Map Handle
      */
-    public static String getMapHandle(long cpHandle, TYPE messageType){
+    public static String getMapHandle(long cpHandle, TYPE messageType) {
         String mapHandle = "-1";
         /* Avoid NPE for possible "null" value of messageType */
-        if(messageType != null) {
-            switch(messageType)
-            {
+        if (messageType != null) {
+            switch (messageType) {
                 case MMS:
                     mapHandle = getLongAsString(cpHandle | HANDLE_TYPE_MMS_MASK);
                     break;
@@ -276,7 +294,9 @@
                     throw new IllegalArgumentException("Message type not supported");
             }
         } else {
-            if(D)Log.e(TAG," Invalid messageType input");
+            if (D) {
+                Log.e(TAG, " Invalid messageType input");
+            }
         }
         return mapHandle;
 
@@ -288,10 +308,9 @@
      * @param messageType message type (TYPE_MMS/TYPE_SMS_GSM/TYPE_SMS_CDMA/TYPE_EMAIL)
      * @return String Formatted Map Handle
      */
-    public static String getMapConvoHandle(long cpHandle, TYPE messageType){
+    public static String getMapConvoHandle(long cpHandle, TYPE messageType) {
         String mapHandle = "-1";
-        switch(messageType)
-        {
+        switch (messageType) {
             case MMS:
             case SMS_GSM:
             case SMS_CDMA:
@@ -313,21 +332,25 @@
      * @param mapHandle the handle string
      * @return the handle value
      */
-    static public long getMsgHandleAsLong(String mapHandle){
+    public static long getMsgHandleAsLong(String mapHandle) {
         return Long.parseLong(mapHandle, 16);
     }
+
     /**
      * Convert a Map Handle into a content provider Handle
      * @param mapHandle handle to convert from
      * @return content provider handle without message type mask
      */
-    static public long getCpHandle(String mapHandle)
-    {
+    public static long getCpHandle(String mapHandle) {
         long cpHandle = getMsgHandleAsLong(mapHandle);
-        if(D)Log.d(TAG,"-> MAP handle:"+mapHandle);
+        if (D) {
+            Log.d(TAG, "-> MAP handle:" + mapHandle);
+        }
         /* remove masks as the call should already know what type of message this handle is for */
         cpHandle &= ~HANDLE_TYPE_MASK;
-        if(D)Log.d(TAG,"->CP handle:"+cpHandle);
+        if (D) {
+            Log.d(TAG, "->CP handle:" + cpHandle);
+        }
 
         return cpHandle;
     }
@@ -337,19 +360,24 @@
      * @param mapHandle
      * @return
      */
-    static public TYPE getMsgTypeFromHandle(String mapHandle) {
+    public static TYPE getMsgTypeFromHandle(String mapHandle) {
         long cpHandle = getMsgHandleAsLong(mapHandle);
 
-        if((cpHandle & HANDLE_TYPE_MMS_MASK) != 0)
+        if ((cpHandle & HANDLE_TYPE_MMS_MASK) != 0) {
             return TYPE.MMS;
-        if((cpHandle & HANDLE_TYPE_EMAIL_MASK) != 0)
+        }
+        if ((cpHandle & HANDLE_TYPE_EMAIL_MASK) != 0) {
             return TYPE.EMAIL;
-        if((cpHandle & HANDLE_TYPE_SMS_GSM_MASK) != 0)
+        }
+        if ((cpHandle & HANDLE_TYPE_SMS_GSM_MASK) != 0) {
             return TYPE.SMS_GSM;
-        if((cpHandle & HANDLE_TYPE_SMS_CDMA_MASK) != 0)
+        }
+        if ((cpHandle & HANDLE_TYPE_SMS_CDMA_MASK) != 0) {
             return TYPE.SMS_CDMA;
-        if((cpHandle & HANDLE_TYPE_IM_MASK) != 0)
+        }
+        if ((cpHandle & HANDLE_TYPE_IM_MASK) != 0) {
             return TYPE.IM;
+        }
 
         throw new IllegalArgumentException("Message type not found in handle string.");
     }
@@ -364,54 +392,53 @@
      * @return the same string if valid, otherwise a new String stripped for
      * any illegal characters. If a null pointer is passed an empty string will be returned.
      */
-    static public String stripInvalidChars(String text) {
-        if(text == null) {
+    public static String stripInvalidChars(String text) {
+        if (text == null) {
             return "";
         }
-        char out[] = new char[text.length()];
+        char[] out = new char[text.length()];
         int i, o, l;
-        for(i=0, o=0, l=text.length(); i<l; i++){
+        for (i = 0, o = 0, l = text.length(); i < l; i++) {
             char c = text.charAt(i);
-            if((c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd)) {
+            if ((c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd)) {
                 out[o++] = c;
             } // Else we skip the character
         }
 
-        if(i==o) {
+        if (i == o) {
             return text;
         } else { // We removed some characters, create the new string
-            return new String(out,0,o);
+            return new String(out, 0, o);
         }
     }
 
     /**
      * Truncate UTF-8 string encoded byte array to desired length
      * @param utf8String String to convert to bytes array h
-     * @param length Max length of byte array returned including null termination
+     * @param maxLength Max length of byte array returned including null termination
      * @return byte array containing valid utf8 characters with max length
      * @throws UnsupportedEncodingException
      */
-    static public byte[] truncateUtf8StringToBytearray(String utf8String, int maxLength)
+    public static byte[] truncateUtf8StringToBytearray(String utf8String, int maxLength)
             throws UnsupportedEncodingException {
 
         byte[] utf8Bytes = new byte[utf8String.length() + 1];
         try {
-            System.arraycopy(utf8String.getBytes("UTF-8"), 0,
-                             utf8Bytes, 0, utf8String.length());
+            System.arraycopy(utf8String.getBytes("UTF-8"), 0, utf8Bytes, 0, utf8String.length());
         } catch (UnsupportedEncodingException e) {
-            Log.e(TAG,"truncateUtf8StringToBytearray: getBytes exception ", e);
+            Log.e(TAG, "truncateUtf8StringToBytearray: getBytes exception ", e);
             throw e;
         }
 
         if (utf8Bytes.length > maxLength) {
             /* if 'continuation' byte is in place 200,
              * then strip previous bytes until utf-8 start byte is found */
-            if ( (utf8Bytes[maxLength - 1] & 0xC0) == 0x80 ) {
+            if ((utf8Bytes[maxLength - 1] & 0xC0) == 0x80) {
                 for (int i = maxLength - 2; i >= 0; i--) {
                     if ((utf8Bytes[i] & 0xC0) == 0xC0) {
                         /* first byte in utf-8 character found,
                          * now copy i - 1 bytes to outBytes and add null termination */
-                        utf8Bytes = Arrays.copyOf(utf8Bytes, i+1);
+                        utf8Bytes = Arrays.copyOf(utf8Bytes, i + 1);
                         utf8Bytes[i] = 0;
                         break;
                     }
@@ -424,49 +451,52 @@
         }
         return utf8Bytes;
     }
-    private static Pattern p = Pattern.compile("=\\?(.+?)\\?(.)\\?(.+?(?=\\?=))\\?=");
+
+    private static final Pattern PATTERN = Pattern.compile("=\\?(.+?)\\?(.)\\?(.+?(?=\\?=))\\?=");
 
     /**
      * Method for converting quoted printable og base64 encoded string from headers.
      * @param in the string with encoding
      * @return decoded string if success - else the same string as was as input.
      */
-    static public String stripEncoding(String in){
+    public static String stripEncoding(String in) {
         String str = null;
-        if(in.contains("=?") && in.contains("?=")){
+        if (in.contains("=?") && in.contains("?=")) {
             String encoding;
             String charset;
             String encodedText;
             String match;
-            Matcher m = p.matcher(in);
-            while(m.find()){
+            Matcher m = PATTERN.matcher(in);
+            while (m.find()) {
                 match = m.group(0);
                 charset = m.group(1);
                 encoding = m.group(2);
                 encodedText = m.group(3);
-                Log.v(TAG, "Matching:" + match +"\nCharset: "+charset +"\nEncoding : " +encoding
-                        + "\nText: " + encodedText);
-                if(encoding.equalsIgnoreCase("Q")){
+                Log.v(TAG,
+                        "Matching:" + match + "\nCharset: " + charset + "\nEncoding : " + encoding
+                                + "\nText: " + encodedText);
+                if (encoding.equalsIgnoreCase("Q")) {
                     //quoted printable
-                    Log.d(TAG,"StripEncoding: Quoted Printable string : " + encodedText);
-                    str = new String(quotedPrintableToUtf8(encodedText,charset));
+                    Log.d(TAG, "StripEncoding: Quoted Printable string : " + encodedText);
+                    str = new String(quotedPrintableToUtf8(encodedText, charset));
                     in = in.replace(match, str);
-                }else if(encoding.equalsIgnoreCase("B")){
+                } else if (encoding.equalsIgnoreCase("B")) {
                     // base64
-                    try{
+                    try {
 
-                        Log.d(TAG,"StripEncoding: base64 string : " + encodedText);
-                        str = new String(Base64.decode(encodedText.getBytes(charset),
-                                Base64.DEFAULT), charset);
-                        Log.d(TAG,"StripEncoding: decoded string : " + str);
+                        Log.d(TAG, "StripEncoding: base64 string : " + encodedText);
+                        str = new String(
+                                Base64.decode(encodedText.getBytes(charset), Base64.DEFAULT),
+                                charset);
+                        Log.d(TAG, "StripEncoding: decoded string : " + str);
                         in = in.replace(match, str);
-                    }catch(UnsupportedEncodingException e){
+                    } catch (UnsupportedEncodingException e) {
                         Log.e(TAG, "stripEncoding: Unsupported charset: " + charset);
-                    }catch (IllegalArgumentException e){
-                        Log.e(TAG,"stripEncoding: string not encoded as base64: " +encodedText);
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "stripEncoding: string not encoded as base64: " + encodedText);
                     }
-                }else{
-                    Log.e(TAG, "stripEncoding: Hit unknown encoding: "+encoding);
+                } else {
+                    Log.e(TAG, "stripEncoding: Hit unknown encoding: " + encoding);
                 }
             }
         }
@@ -487,47 +517,61 @@
         try {
             input = text.getBytes("US-ASCII");
         } catch (UnsupportedEncodingException e) {
-            /* This cannot happen as "US-ASCII" is supported for all Java implementations */ }
+            /* This cannot happen as "US-ASCII" is supported for all Java implementations */
+        }
 
-        if(input == null){
+        if (input == null) {
             return "".getBytes();
         }
 
-        int in, out, stopCnt = input.length-2; // Leave room for peaking the next two bytes
+        int in, out, stopCnt = input.length - 2; // Leave room for peaking the next two bytes
 
         /* Algorithm:
          *  - Search for token, copying all non token chars
          * */
-        for(in=0, out=0; in < stopCnt; in++){
+        for (in = 0, out = 0; in < stopCnt; in++) {
             byte b0 = input[in];
-            if(b0 == '=') {
+            if (b0 == '=') {
                 byte b1 = input[++in];
                 byte b2 = input[++in];
-                if(b1 == '\r' && b2 == '\n') {
+                if (b1 == '\r' && b2 == '\n') {
                     continue; // soft line break, remove all tree;
                 }
-                if(((b1 >= '0' && b1 <= '9') || (b1 >= 'A' && b1 <= 'F')
-                        || (b1 >= 'a' && b1 <= 'f'))
-                        && ((b2 >= '0' && b2 <= '9') || (b2 >= 'A' && b2 <= 'F')
-                        || (b2 >= 'a' && b2 <= 'f'))) {
-                    if(V)Log.v(TAG, "Found hex number: " + String.format("%c%c", b1, b2));
-                    if(b1 <= '9')       b1 = (byte) (b1 - '0');
-                    else if (b1 <= 'F') b1 = (byte) (b1 - 'A' + 10);
-                    else if (b1 <= 'f') b1 = (byte) (b1 - 'a' + 10);
+                if (((b1 >= '0' && b1 <= '9') || (b1 >= 'A' && b1 <= 'F') || (b1 >= 'a'
+                        && b1 <= 'f')) && ((b2 >= '0' && b2 <= '9') || (b2 >= 'A' && b2 <= 'F') || (
+                        b2 >= 'a' && b2 <= 'f'))) {
+                    if (V) {
+                        Log.v(TAG, "Found hex number: " + String.format("%c%c", b1, b2));
+                    }
+                    if (b1 <= '9') {
+                        b1 = (byte) (b1 - '0');
+                    } else if (b1 <= 'F') {
+                        b1 = (byte) (b1 - 'A' + 10);
+                    } else if (b1 <= 'f') {
+                        b1 = (byte) (b1 - 'a' + 10);
+                    }
 
-                    if(b2 <= '9')       b2 = (byte) (b2 - '0');
-                    else if (b2 <= 'F') b2 = (byte) (b2 - 'A' + 10);
-                    else if (b2 <= 'f') b2 = (byte) (b2 - 'a' + 10);
+                    if (b2 <= '9') {
+                        b2 = (byte) (b2 - '0');
+                    } else if (b2 <= 'F') {
+                        b2 = (byte) (b2 - 'A' + 10);
+                    } else if (b2 <= 'f') {
+                        b2 = (byte) (b2 - 'a' + 10);
+                    }
 
-                    if(V)Log.v(TAG, "Resulting nibble values: " +
-                            String.format("b1=%x b2=%x", b1, b2));
+                    if (V) {
+                        Log.v(TAG,
+                                "Resulting nibble values: " + String.format("b1=%x b2=%x", b1, b2));
+                    }
 
-                    output[out++] = (byte)(b1<<4 | b2); // valid hex char, append
-                    if(V)Log.v(TAG, "Resulting value: "  + String.format("0x%2x", output[out-1]));
+                    output[out++] = (byte) (b1 << 4 | b2); // valid hex char, append
+                    if (V) {
+                        Log.v(TAG, "Resulting value: " + String.format("0x%2x", output[out - 1]));
+                    }
                     continue;
                 }
-                Log.w(TAG, "Received wrongly quoted printable encoded text. " +
-                        "Continuing at best effort...");
+                Log.w(TAG, "Received wrongly quoted printable encoded text. "
+                        + "Continuing at best effort...");
                 /* If we get a '=' without either a hex value or CRLF following, just add it and
                  * rewind the in counter. */
                 output[out++] = b0;
@@ -548,12 +592,12 @@
         String result = null;
         // Figure out if we support the charset, else fall back to UTF-8, as this is what
         // the MAP specification suggest to use, and is compatible with US-ASCII.
-        if(charset == null){
+        if (charset == null) {
             charset = "UTF-8";
         } else {
             charset = charset.toUpperCase();
             try {
-                if(Charset.isSupported(charset) == false) {
+                if (!Charset.isSupported(charset)) {
                     charset = "UTF-8";
                 }
             } catch (IllegalCharsetNameException e) {
@@ -561,13 +605,15 @@
                 charset = "UTF-8";
             }
         }
-        try{
+        try {
             result = new String(output, 0, out, charset);
         } catch (UnsupportedEncodingException e) {
             /* This cannot happen unless Charset.isSupported() is out of sync with String */
-            try{
+            try {
                 result = new String(output, 0, out, "UTF-8");
-            } catch (UnsupportedEncodingException e2) {/* This cannot happen */}
+            } catch (UnsupportedEncodingException e2) {
+                Log.e(TAG, "quotedPrintableToUtf8: " + e);
+            }
         }
         return result.getBytes(); /* return the result as "UTF-8" bytes */
     }
@@ -582,9 +628,9 @@
      * @return UTF-8 string containing quoted-printable characters
      */
 
-    private static byte ESCAPE_CHAR = '=';
-    private static byte TAB = 9;
-    private static byte SPACE = 32;
+    private static final byte ESCAPE_CHAR = '=';
+    private static final byte TAB = 9;
+    private static final byte SPACE = 32;
 
     public static final String encodeQuotedPrintable(byte[] bytes) {
         if (bytes == null) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessage.java b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
index c4d28ad..e222aa9 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessage.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessage.java
@@ -14,6 +14,12 @@
 */
 package com.android.bluetooth.map;
 
+import android.os.Environment;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -24,21 +30,15 @@
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 
-import android.os.Environment;
-import android.telephony.PhoneNumberUtils;
-import android.util.Log;
-
-import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
-
 public abstract class BluetoothMapbMessage {
 
-    protected static String TAG = "BluetoothMapbMessage";
+    protected static final String TAG = "BluetoothMapbMessage";
     protected static final boolean D = BluetoothMapService.DEBUG;
     protected static final boolean V = BluetoothMapService.VERBOSE;
 
     private String mVersionString = "VERSION:1.0";
 
-    public static int INVALID_VALUE = -1;
+    public static final int INVALID_VALUE = -1;
 
     protected int mAppParamCharset = BluetoothMapAppParams.INVALID_VALUE_PARAMETER;
 
@@ -56,11 +56,11 @@
 
     private int mBMsgLength = INVALID_VALUE;
 
-    private ArrayList<vCard> mOriginator = null;
-    private ArrayList<vCard> mRecipient = null;
+    private ArrayList<VCard> mOriginator = null;
+    private ArrayList<VCard> mRecipient = null;
 
 
-    public static class vCard {
+    public static class VCard {
         /* VCARD attributes */
         private String mVersion;
         private String mName = null;
@@ -77,17 +77,18 @@
          * @param formattedName Formatted name
          * @param phoneNumbers a String[] of phone numbers
          * @param emailAddresses a String[] of email addresses
-         * @param the bmessage envelope level (0 is the top/most outer level)
+         * @param envLevel the bmessage envelope level (0 is the top/most outer level)
          */
-        public vCard(String name, String formattedName, String[] phoneNumbers,
+        public VCard(String name, String formattedName, String[] phoneNumbers,
                 String[] emailAddresses, int envLevel) {
             this.mEnvLevel = envLevel;
             this.mVersion = "3.0";
             this.mName = name != null ? name : "";
             this.mFormattedName = formattedName != null ? formattedName : "";
             setPhoneNumbers(phoneNumbers);
-            if (emailAddresses != null)
+            if (emailAddresses != null) {
                 this.mEmailAddresses = emailAddresses;
+            }
         }
 
         /**
@@ -95,16 +96,16 @@
          * @param name Structured name
          * @param phoneNumbers a String[] of phone numbers
          * @param emailAddresses a String[] of email addresses
-         * @param the bmessage envelope level (0 is the top/most outer level)
+         * @param envLevel the bmessage envelope level (0 is the top/most outer level)
          */
-        public vCard(String name, String[] phoneNumbers,
-                String[] emailAddresses, int envLevel) {
+        public VCard(String name, String[] phoneNumbers, String[] emailAddresses, int envLevel) {
             this.mEnvLevel = envLevel;
             this.mVersion = "2.1";
             this.mName = name != null ? name : "";
             setPhoneNumbers(phoneNumbers);
-            if (emailAddresses != null)
+            if (emailAddresses != null) {
                 this.mEmailAddresses = emailAddresses;
+            }
         }
 
         /**
@@ -116,11 +117,8 @@
          * @param btUids a String[] of X-BT-UIDs if available, else null
          * @param btUcis a String[] of X-BT-UCIs if available, else null
          */
-        public vCard(String name, String formattedName,
-                     String[] phoneNumbers,
-                     String[] emailAddresses,
-                     String[] btUids,
-                     String[] btUcis) {
+        public VCard(String name, String formattedName, String[] phoneNumbers,
+                String[] emailAddresses, String[] btUids, String[] btUcis) {
             this.mVersion = "3.0";
             this.mName = (name != null) ? name : "";
             this.mFormattedName = (formattedName != null) ? formattedName : "";
@@ -139,18 +137,19 @@
          * @param phoneNumbers a String[] of phone numbers
          * @param emailAddresses a String[] of email addresses
          */
-        public vCard(String name, String[] phoneNumbers, String[] emailAddresses) {
+        public VCard(String name, String[] phoneNumbers, String[] emailAddresses) {
             this.mVersion = "2.1";
             this.mName = name != null ? name : "";
             setPhoneNumbers(phoneNumbers);
-            if (emailAddresses != null)
+            if (emailAddresses != null) {
                 this.mEmailAddresses = emailAddresses;
+            }
         }
 
         private void setPhoneNumbers(String[] numbers) {
-            if(numbers != null && numbers.length > 0) {
+            if (numbers != null && numbers.length > 0) {
                 mPhoneNumbers = new String[numbers.length];
-                for(int i = 0, n = numbers.length; i < n; i++){
+                for (int i = 0, n = numbers.length; i < n; i++) {
                     String networkNumber = PhoneNumberUtils.extractNetworkPortion(numbers[i]);
                     /* extractNetworkPortion can return N if the number is a service
                      * "number" = a string with the a name in (i.e. "Some-Tele-company" would
@@ -159,10 +158,10 @@
                      * */
                     String strippedNumber = PhoneNumberUtils.stripSeparators(numbers[i]);
                     Boolean alpha = false;
-                    if(strippedNumber != null){
+                    if (strippedNumber != null) {
                         alpha = strippedNumber.matches("[0-9]*[a-zA-Z]+[0-9]*");
                     }
-                    if(networkNumber != null && networkNumber.length() > 1 && !alpha) {
+                    if (networkNumber != null && networkNumber.length() > 1 && !alpha) {
                         mPhoneNumbers[i] = networkNumber;
                     } else {
                         mPhoneNumbers[i] = numbers[i];
@@ -172,10 +171,11 @@
         }
 
         public String getFirstPhoneNumber() {
-            if(mPhoneNumbers.length > 0) {
+            if (mPhoneNumbers.length > 0) {
                 return mPhoneNumbers[0];
-            } else
+            } else {
                 return null;
+            }
         }
 
         public int getEnvLevel() {
@@ -187,49 +187,48 @@
         }
 
         public String getFirstEmail() {
-            if(mEmailAddresses.length > 0) {
+            if (mEmailAddresses.length > 0) {
                 return mEmailAddresses[0];
-            } else
+            } else {
                 return null;
+            }
         }
+
         public String getFirstBtUci() {
-            if(mBtUcis.length > 0) {
+            if (mBtUcis.length > 0) {
                 return mBtUcis[0];
-            } else
+            } else {
                 return null;
+            }
         }
 
         public String getFirstBtUid() {
-            if(mBtUids.length > 0) {
+            if (mBtUids.length > 0) {
                 return mBtUids[0];
-            } else
+            } else {
                 return null;
+            }
         }
 
-        public void encode(StringBuilder sb)
-        {
+        public void encode(StringBuilder sb) {
             sb.append("BEGIN:VCARD").append("\r\n");
             sb.append("VERSION:").append(mVersion).append("\r\n");
-            if(mVersion.equals("3.0") && mFormattedName != null)
-            {
+            if (mVersion.equals("3.0") && mFormattedName != null) {
                 sb.append("FN:").append(mFormattedName).append("\r\n");
             }
-            if (mName != null)
+            if (mName != null) {
                 sb.append("N:").append(mName).append("\r\n");
-            for(String phoneNumber : mPhoneNumbers)
-            {
+            }
+            for (String phoneNumber : mPhoneNumbers) {
                 sb.append("TEL:").append(phoneNumber).append("\r\n");
             }
-            for(String emailAddress : mEmailAddresses)
-            {
+            for (String emailAddress : mEmailAddresses) {
                 sb.append("EMAIL:").append(emailAddress).append("\r\n");
             }
-            for(String btUid : mBtUids)
-            {
+            for (String btUid : mBtUids) {
                 sb.append("X-BT-UID:").append(btUid).append("\r\n");
             }
-            for(String btUci : mBtUcis)
-            {
+            for (String btUci : mBtUcis) {
                 sb.append("X-BT-UCI:").append(btUci).append("\r\n");
             }
             sb.append("END:VCARD").append("\r\n");
@@ -242,7 +241,7 @@
          * @param envLevel
          * @return
          */
-        public static vCard parseVcard(BMsgReader reader, int envLevel) {
+        public static VCard parseVcard(BMsgReader reader, int envLevel) {
             String formattedName = null;
             String name = null;
             ArrayList<String> phoneNumbers = null;
@@ -252,81 +251,82 @@
             String[] parts;
             String line = reader.getLineEnforce();
 
-            while(!line.contains("END:VCARD")){
+            while (!line.contains("END:VCARD")) {
                 line = line.trim();
-                if(line.startsWith("N:")){
+                if (line.startsWith("N:")) {
                     parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
-                    if(parts.length == 2) {
+                    if (parts.length == 2) {
                         name = parts[1];
-                    } else
+                    } else {
                         name = "";
-                }
-                else if(line.startsWith("FN:")){
+                    }
+                } else if (line.startsWith("FN:")) {
                     parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
-                    if(parts.length == 2) {
+                    if (parts.length == 2) {
                         formattedName = parts[1];
-                    } else
+                    } else {
                         formattedName = "";
-                }
-                else if(line.startsWith("TEL:")){
+                    }
+                } else if (line.startsWith("TEL:")) {
                     parts = line.split("[^\\\\]:"); // Split on "un-escaped" ':'
-                    if(parts.length == 2) {
+                    if (parts.length == 2) {
                         String[] subParts = parts[1].split("[^\\\\];");
-                        if(phoneNumbers == null)
+                        if (phoneNumbers == null) {
                             phoneNumbers = new ArrayList<String>(1);
+                        }
                         // only keep actual phone number
-                        phoneNumbers.add(subParts[subParts.length-1]);
-                    } else {}
-                        // Empty phone number - ignore
-                }
-                else if(line.startsWith("EMAIL:")){
+                        phoneNumbers.add(subParts[subParts.length - 1]);
+                    }
+                    // Empty phone number - ignore
+                } else if (line.startsWith("EMAIL:")) {
                     parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
-                    if(parts.length == 2) {
+                    if (parts.length == 2) {
                         String[] subParts = parts[1].split("[^\\\\];");
-                        if(emailAddresses == null)
+                        if (emailAddresses == null) {
                             emailAddresses = new ArrayList<String>(1);
+                        }
                         // only keep actual email address
-                        emailAddresses.add(subParts[subParts.length-1]);
-                    } else {}
-                        // Empty email address entry - ignore
-                }
-                else if(line.startsWith("X-BT-UCI:")){
+                        emailAddresses.add(subParts[subParts.length - 1]);
+                    }
+                    // Empty email address entry - ignore
+                } else if (line.startsWith("X-BT-UCI:")) {
                     parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
-                    if(parts.length == 2) {
+                    if (parts.length == 2) {
                         String[] subParts = parts[1].split("[^\\\\];");
-                        if(btUcis == null)
+                        if (btUcis == null) {
                             btUcis = new ArrayList<String>(1);
-                        btUcis.add(subParts[subParts.length-1]); // only keep actual UCI
-                    } else {}
-                        // Empty UCIentry - ignore
-                }
-                else if(line.startsWith("X-BT-UID:")){
+                        }
+                        btUcis.add(subParts[subParts.length - 1]); // only keep actual UCI
+                    }
+                    // Empty UCIentry - ignore
+                } else if (line.startsWith("X-BT-UID:")) {
                     parts = line.split("[^\\\\]:"); // Split on "un-escaped" :
-                    if(parts.length == 2) {
+                    if (parts.length == 2) {
                         String[] subParts = parts[1].split("[^\\\\];");
-                        if(btUids == null)
+                        if (btUids == null) {
                             btUids = new ArrayList<String>(1);
-                        btUids.add(subParts[subParts.length-1]); // only keep actual UID
-                    } else {}
-                        // Empty UID entry - ignore
+                        }
+                        btUids.add(subParts[subParts.length - 1]); // only keep actual UID
+                    }
+                    // Empty UID entry - ignore
                 }
 
 
                 line = reader.getLineEnforce();
             }
-            return new vCard(name, formattedName,
-                    phoneNumbers == null?
-                            null : phoneNumbers.toArray(new String[phoneNumbers.size()]),
-                    emailAddresses == null ?
-                            null : emailAddresses.toArray(new String[emailAddresses.size()]),
-                    envLevel);
+            return new VCard(name, formattedName, phoneNumbers == null ? null
+                    : phoneNumbers.toArray(new String[phoneNumbers.size()]),
+                    emailAddresses == null ? null
+                            : emailAddresses.toArray(new String[emailAddresses.size()]), envLevel);
         }
-    };
+    }
+
+    ;
 
     private static class BMsgReader {
         InputStream mInStream;
-        public BMsgReader(InputStream is)
-        {
+
+        BMsgReader(InputStream is) {
             this.mInStream = is;
         }
 
@@ -346,10 +346,11 @@
                 while ((readByte = mInStream.read()) != -1) {
                     if (readByte == '\r') {
                         if ((readByte = mInStream.read()) != -1 && readByte == '\n') {
-                            if(output.size() == 0)
+                            if (output.size() == 0) {
                                 continue; /* Skip empty lines */
-                            else
+                            } else {
                                 break;
+                            }
                         } else {
                             output.write('\r');
                         }
@@ -374,10 +375,11 @@
         public String getLine() {
             try {
                 byte[] line = getLineAsBytes();
-                if (line.length == 0)
+                if (line.length == 0) {
                     return null;
-                else
+                } else {
                     return new String(line, "UTF-8");
+                }
             } catch (UnsupportedEncodingException e) {
                 Log.w(TAG, e);
                 return null;
@@ -390,11 +392,12 @@
          * @return the next line
          */
         public String getLineEnforce() {
-        String line = getLine();
-        if (line == null)
-            throw new IllegalArgumentException("Bmessage too short");
+            String line = getLine();
+            if (line == null) {
+                throw new IllegalArgumentException("Bmessage too short");
+            }
 
-        return line;
+            return line;
         }
 
 
@@ -407,13 +410,14 @@
          * If the expected substring is not found.
          *
          */
-        public void expect(String subString) throws IllegalArgumentException{
+        public void expect(String subString) throws IllegalArgumentException {
             String line = getLine();
-            if(line == null || subString == null){
+            if (line == null || subString == null) {
                 throw new IllegalArgumentException("Line or substring is null");
-            }else if(!line.toUpperCase().contains(subString.toUpperCase()))
-                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
-                                                    + line + "\"");
+            } else if (!line.toUpperCase().contains(subString.toUpperCase())) {
+                throw new IllegalArgumentException(
+                        "Expected \"" + subString + "\" in: \"" + line + "\"");
+            }
         }
 
         /**
@@ -423,14 +427,16 @@
          * @throws IllegalArgumentException
          * If one or all of the strings are not found.
          */
-        public void expect(String subString, String subString2) throws IllegalArgumentException{
+        public void expect(String subString, String subString2) throws IllegalArgumentException {
             String line = getLine();
-            if(!line.toUpperCase().contains(subString.toUpperCase()))
-                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
-                                                   + line + "\"");
-            if(!line.toUpperCase().contains(subString2.toUpperCase()))
-                throw new IllegalArgumentException("Expected \"" + subString + "\" in: \""
-                                                   + line + "\"");
+            if (!line.toUpperCase().contains(subString.toUpperCase())) {
+                throw new IllegalArgumentException(
+                        "Expected \"" + subString + "\" in: \"" + line + "\"");
+            }
+            if (!line.toUpperCase().contains(subString2.toUpperCase())) {
+                throw new IllegalArgumentException(
+                        "Expected \"" + subString + "\" in: \"" + line + "\"");
+            }
         }
 
         /**
@@ -443,11 +449,12 @@
             byte[] data = new byte[length];
             try {
                 int bytesRead;
-                int offset=0;
-                while ((bytesRead = mInStream.read(data, offset, length-offset))
-                                 != (length - offset)) {
-                    if(bytesRead == -1)
+                int offset = 0;
+                while ((bytesRead = mInStream.read(data, offset, length - offset)) != (length
+                        - offset)) {
+                    if (bytesRead == -1) {
                         return null;
+                    }
                     offset += bytesRead;
                 }
             } catch (IOException e) {
@@ -456,25 +463,28 @@
             }
             return data;
         }
-    };
+    }
 
-    public BluetoothMapbMessage(){
+    ;
+
+    public BluetoothMapbMessage() {
 
     }
 
     public String getVersionString() {
         return mVersionString;
     }
+
     /**
      * Set the version string for VCARD
      * @param version the actual number part of the version string i.e. 1.0
      * */
     public void setVersionString(String version) {
-        this.mVersionString = "VERSION:"+version;
+        this.mVersionString = "VERSION:" + version;
     }
 
-    public static BluetoothMapbMessage parse(InputStream bMsgStream,
-                                             int appParamCharset) throws IllegalArgumentException{
+    public static BluetoothMapbMessage parse(InputStream bMsgStream, int appParamCharset)
+            throws IllegalArgumentException {
         BMsgReader reader;
         String line = "";
         BluetoothMapbMessage newBMsg = null;
@@ -488,10 +498,10 @@
          * If an error occurs, it will result in a OBEX_HTTP_PRECON_FAILED to be send to the client,
          * even though the message might be formatted correctly, hence only enable this code for
          * test. */
-        if(V) {
+        if (V) {
             /* Read the entire stream into a file on the SD card*/
             File sdCard = Environment.getExternalStorageDirectory();
-            File dir = new File (sdCard.getAbsolutePath() + "/bluetooth/log/");
+            File dir = new File(sdCard.getAbsolutePath() + "/bluetooth/log/");
             dir.mkdirs();
             File file = new File(dir, "receivedBMessage.txt");
             FileOutputStream outStream = null;
@@ -502,29 +512,31 @@
                 /* overwrite if it does already exist */
                 outStream = new FileOutputStream(file, false);
 
-                byte[] buffer = new byte[4*1024];
+                byte[] buffer = new byte[4 * 1024];
                 int len = 0;
                 while ((len = bMsgStream.read(buffer)) > 0) {
                     outStream.write(buffer, 0, len);
                     writtenLen += len;
                 }
             } catch (FileNotFoundException e) {
-                Log.e(TAG,"Unable to create output stream",e);
+                Log.e(TAG, "Unable to create output stream", e);
             } catch (IOException e) {
-                Log.e(TAG,"Failed to copy the received message",e);
-                if(writtenLen != 0)
+                Log.e(TAG, "Failed to copy the received message", e);
+                if (writtenLen != 0) {
                     failed = true; /* We failed to write the complete file,
                                       hence the received message is lost... */
+                }
             } finally {
-                if(outStream != null)
+                if (outStream != null) {
                     try {
                         outStream.close();
                     } catch (IOException e) {
                     }
+                }
             }
 
             /* Return if we corrupted the incoming bMessage. */
-            if(failed) {
+            if (failed) {
                 throw new IllegalArgumentException(); /* terminate this function with an error. */
             }
 
@@ -541,7 +553,7 @@
                 try {
                     bMsgStream = new FileInputStream(file);
                 } catch (FileNotFoundException e) {
-                    Log.e(TAG,"Failed to open the bMessage file", e);
+                    Log.e(TAG, "Failed to open the bMessage file", e);
                     /* terminate this function with an error */
                     throw new IllegalArgumentException();
                 }
@@ -555,14 +567,14 @@
 
         line = reader.getLineEnforce();
         // Parse the properties - which end with either a VCARD or a BENV
-        while(!line.contains("BEGIN:VCARD") && !line.contains("BEGIN:BENV")) {
-            if(line.contains("STATUS")){
-                String arg[] = line.split(":");
+        while (!line.contains("BEGIN:VCARD") && !line.contains("BEGIN:BENV")) {
+            if (line.contains("STATUS")) {
+                String[] arg = line.split(":");
                 if (arg != null && arg.length == 2) {
                     if (arg[1].trim().equals("READ")) {
                         status = true;
                     } else if (arg[1].trim().equals("UNREAD")) {
-                        status =false;
+                        status = false;
                     } else {
                         throw new IllegalArgumentException("Wrong value in 'STATUS': " + arg[1]);
                     }
@@ -570,47 +582,47 @@
                     throw new IllegalArgumentException("Missing value for 'STATUS': " + line);
                 }
             }
-            if(line.contains("EXTENDEDDATA")){
-                String arg[] = line.split(":");
+            if (line.contains("EXTENDEDDATA")) {
+                String[] arg = line.split(":");
                 if (arg != null && arg.length == 2) {
                     String value = arg[1].trim();
                     //FIXME what should we do with this
-                    Log.i(TAG,"We got extended data with: "+value);
+                    Log.i(TAG, "We got extended data with: " + value);
                 }
             }
-            if(line.contains("TYPE")) {
-                String arg[] = line.split(":");
+            if (line.contains("TYPE")) {
+                String[] arg = line.split(":");
                 if (arg != null && arg.length == 2) {
                     String value = arg[1].trim();
                     /* Will throw IllegalArgumentException if value is wrong */
                     type = TYPE.valueOf(value);
-                    if(appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE
+                    if (appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE
                             && type != TYPE.SMS_CDMA && type != TYPE.SMS_GSM) {
-                        throw new IllegalArgumentException("Native appParamsCharset "
-                                                             +"only supported for SMS");
+                        throw new IllegalArgumentException(
+                                "Native appParamsCharset " + "only supported for SMS");
                     }
-                    switch(type) {
-                    case SMS_CDMA:
-                    case SMS_GSM:
-                        newBMsg = new BluetoothMapbMessageSms();
-                        break;
-                    case MMS:
-                        newBMsg = new BluetoothMapbMessageMime();
-                        break;
-                    case EMAIL:
-                        newBMsg = new BluetoothMapbMessageEmail();
-                        break;
-                    case IM:
-                        newBMsg = new BluetoothMapbMessageMime();
-                        break;
-                    default:
-                        break;
+                    switch (type) {
+                        case SMS_CDMA:
+                        case SMS_GSM:
+                            newBMsg = new BluetoothMapbMessageSms();
+                            break;
+                        case MMS:
+                            newBMsg = new BluetoothMapbMessageMime();
+                            break;
+                        case EMAIL:
+                            newBMsg = new BluetoothMapbMessageEmail();
+                            break;
+                        case IM:
+                            newBMsg = new BluetoothMapbMessageMime();
+                            break;
+                        default:
+                            break;
                     }
                 } else {
                     throw new IllegalArgumentException("Missing value for 'TYPE':" + line);
                 }
             }
-            if(line.contains("FOLDER")) {
+            if (line.contains("FOLDER")) {
                 String[] arg = line.split(":");
                 if (arg != null && arg.length == 2) {
                     folder = arg[1].trim();
@@ -619,26 +631,32 @@
             }
             line = reader.getLineEnforce();
         }
-        if(newBMsg == null)
-            throw new IllegalArgumentException("Missing bMessage TYPE: "+
-                                                    "- unable to parse body-content");
+        if (newBMsg == null) {
+            throw new IllegalArgumentException(
+                    "Missing bMessage TYPE: " + "- unable to parse body-content");
+        }
         newBMsg.setType(type);
         newBMsg.mAppParamCharset = appParamCharset;
-        if(folder != null)
+        if (folder != null) {
             newBMsg.setCompleteFolder(folder);
-        if(statusFound)
+        }
+        if (statusFound) {
             newBMsg.setStatus(status);
+        }
 
         // Now check for originator VCARDs
-        while(line.contains("BEGIN:VCARD")){
-            if(D) Log.d(TAG,"Decoding vCard");
-            newBMsg.addOriginator(vCard.parseVcard(reader,0));
+        while (line.contains("BEGIN:VCARD")) {
+            if (D) {
+                Log.d(TAG, "Decoding vCard");
+            }
+            newBMsg.addOriginator(VCard.parseVcard(reader, 0));
             line = reader.getLineEnforce();
         }
-        if(line.contains("BEGIN:BENV")) {
+        if (line.contains("BEGIN:BENV")) {
             newBMsg.parseEnvelope(reader, 0);
-        } else
+        } else {
             throw new IllegalArgumentException("Bmessage has no BEGIN:BENV - line:" + line);
+        }
 
         /* TODO: Do we need to validate the END:* tags? They are only needed if someone puts
          *        additional info below the END:MSG - in which case we don't handle it.
@@ -659,21 +677,30 @@
     private void parseEnvelope(BMsgReader reader, int level) {
         String line;
         line = reader.getLineEnforce();
-        if(D) Log.d(TAG,"Decoding envelope level " + level);
+        if (D) {
+            Log.d(TAG, "Decoding envelope level " + level);
+        }
 
-       while(line.contains("BEGIN:VCARD")){
-           if(D) Log.d(TAG,"Decoding recipient vCard level " + level);
-            if(mRecipient == null)
-                mRecipient = new ArrayList<vCard>(1);
-            mRecipient.add(vCard.parseVcard(reader, level));
+        while (line.contains("BEGIN:VCARD")) {
+            if (D) {
+                Log.d(TAG, "Decoding recipient vCard level " + level);
+            }
+            if (mRecipient == null) {
+                mRecipient = new ArrayList<VCard>(1);
+            }
+            mRecipient.add(VCard.parseVcard(reader, level));
             line = reader.getLineEnforce();
         }
-        if(line.contains("BEGIN:BENV")) {
-            if(D) Log.d(TAG,"Decoding nested envelope");
+        if (line.contains("BEGIN:BENV")) {
+            if (D) {
+                Log.d(TAG, "Decoding nested envelope");
+            }
             parseEnvelope(reader, ++level); // Nested BENV
         }
-        if(line.contains("BEGIN:BBODY")){
-            if(D) Log.d(TAG,"Decoding bbody");
+        if (line.contains("BEGIN:BBODY")) {
+            if (D) {
+                Log.d(TAG, "Decoding bbody");
+            }
             parseBody(reader);
         }
     }
@@ -682,48 +709,44 @@
         String line;
         line = reader.getLineEnforce();
         parseMsgInit();
-        while(!line.contains("END:")) {
-            if(line.contains("PARTID:")) {
-                String arg[] = line.split(":");
+        while (!line.contains("END:")) {
+            if (line.contains("PARTID:")) {
+                String[] arg = line.split(":");
                 if (arg != null && arg.length == 2) {
                     try {
-                    mPartId = Long.parseLong(arg[1].trim());
+                        mPartId = Long.parseLong(arg[1].trim());
                     } catch (NumberFormatException e) {
                         throw new IllegalArgumentException("Wrong value in 'PARTID': " + arg[1]);
                     }
                 } else {
                     throw new IllegalArgumentException("Missing value for 'PARTID': " + line);
                 }
-            }
-            else if(line.contains("ENCODING:")) {
-                String arg[] = line.split(":");
+            } else if (line.contains("ENCODING:")) {
+                String[] arg = line.split(":");
                 if (arg != null && arg.length == 2) {
                     mEncoding = arg[1].trim();
                     // If needed validation will be done when the value is used
                 } else {
                     throw new IllegalArgumentException("Missing value for 'ENCODING': " + line);
                 }
-            }
-            else if(line.contains("CHARSET:")) {
-                String arg[] = line.split(":");
+            } else if (line.contains("CHARSET:")) {
+                String[] arg = line.split(":");
                 if (arg != null && arg.length == 2) {
                     mCharset = arg[1].trim();
                     // If needed validation will be done when the value is used
                 } else {
                     throw new IllegalArgumentException("Missing value for 'CHARSET': " + line);
                 }
-            }
-            else if(line.contains("LANGUAGE:")) {
-                String arg[] = line.split(":");
+            } else if (line.contains("LANGUAGE:")) {
+                String[] arg = line.split(":");
                 if (arg != null && arg.length == 2) {
                     mLanguage = arg[1].trim();
                     // If needed validation will be done when the value is used
                 } else {
                     throw new IllegalArgumentException("Missing value for 'LANGUAGE': " + line);
                 }
-            }
-            else if(line.contains("LENGTH:")) {
-                String arg[] = line.split(":");
+            } else if (line.contains("LENGTH:")) {
+                String[] arg = line.split(":");
                 if (arg != null && arg.length == 2) {
                     try {
                         mBMsgLength = Integer.parseInt(arg[1].trim());
@@ -733,12 +756,14 @@
                 } else {
                     throw new IllegalArgumentException("Missing value for 'LENGTH': " + line);
                 }
-            }
-            else if(line.contains("BEGIN:MSG")) {
-                if (V) Log.v(TAG, "bMsgLength: " + mBMsgLength);
-                if(mBMsgLength == INVALID_VALUE)
-                    throw new IllegalArgumentException("Missing value for 'LENGTH'. " +
-                            "Unable to read remaining part of the message");
+            } else if (line.contains("BEGIN:MSG")) {
+                if (V) {
+                    Log.v(TAG, "bMsgLength: " + mBMsgLength);
+                }
+                if (mBMsgLength == INVALID_VALUE) {
+                    throw new IllegalArgumentException("Missing value for 'LENGTH'. "
+                            + "Unable to read remaining part of the message");
+                }
 
                 /* For SMS: Encoding of MSG is always UTF-8 compliant, regardless of any properties,
                    since PDUs are encodes as hex-strings */
@@ -751,10 +776,10 @@
 
                 // Read until we receive END:MSG as some carkits send bad message lengths
                 String data = "";
-                String message_line = "";
-                while (!message_line.equals("END:MSG")) {
-                    data += message_line;
-                    message_line = reader.getLineEnforce();
+                String messageLine = "";
+                while (!messageLine.equals("END:MSG")) {
+                    data += messageLine;
+                    messageLine = reader.getLineEnforce();
                 }
 
                 // The MAP spec says that all END:MSG strings in the body
@@ -774,6 +799,7 @@
      * @param msgPart
      */
     public abstract void parseMsgPart(String msgPart);
+
     /**
      * Set initial values before parsing - will be called is a message body is found
      * during parsing.
@@ -783,10 +809,11 @@
     public abstract byte[] encode() throws UnsupportedEncodingException;
 
     public void setStatus(boolean read) {
-        if(read)
+        if (read) {
             this.mStatus = "READ";
-        else
+        } else {
             this.mStatus = "UNREAD";
+        }
     }
 
     public void setType(TYPE type) {
@@ -817,13 +844,14 @@
         this.mEncoding = encoding;
     }
 
-    public ArrayList<vCard> getOriginators() {
+    public ArrayList<VCard> getOriginators() {
         return mOriginator;
     }
 
-    public void addOriginator(vCard originator) {
-        if(this.mOriginator == null)
-            this.mOriginator = new ArrayList<vCard>();
+    public void addOriginator(VCard originator) {
+        if (this.mOriginator == null) {
+            this.mOriginator = new ArrayList<VCard>();
+        }
         this.mOriginator.add(originator);
     }
 
@@ -834,22 +862,21 @@
      * @param phoneNumbers
      * @param emailAddresses
      */
-    public void addOriginator(String name, String formattedName,
-                              String[] phoneNumbers,
-                              String[] emailAddresses,
-                              String[] btUids,
-                              String[] btUcis) {
-        if(mOriginator == null)
-            mOriginator = new ArrayList<vCard>();
-        mOriginator.add(new vCard(name, formattedName, phoneNumbers,
-                    emailAddresses, btUids, btUcis));
+    public void addOriginator(String name, String formattedName, String[] phoneNumbers,
+            String[] emailAddresses, String[] btUids, String[] btUcis) {
+        if (mOriginator == null) {
+            mOriginator = new ArrayList<VCard>();
+        }
+        mOriginator.add(
+                new VCard(name, formattedName, phoneNumbers, emailAddresses, btUids, btUcis));
     }
 
 
     public void addOriginator(String[] btUcis, String[] btUids) {
-        if(mOriginator == null)
-            mOriginator = new ArrayList<vCard>();
-        mOriginator.add(new vCard(null,null,null,null,btUids, btUcis));
+        if (mOriginator == null) {
+            mOriginator = new ArrayList<VCard>();
+        }
+        mOriginator.add(new VCard(null, null, null, null, btUids, btUcis));
     }
 
 
@@ -860,40 +887,44 @@
      * @param emailAddresses
      */
     public void addOriginator(String name, String[] phoneNumbers, String[] emailAddresses) {
-        if(mOriginator == null)
-            mOriginator = new ArrayList<vCard>();
-        mOriginator.add(new vCard(name, phoneNumbers, emailAddresses));
+        if (mOriginator == null) {
+            mOriginator = new ArrayList<VCard>();
+        }
+        mOriginator.add(new VCard(name, phoneNumbers, emailAddresses));
     }
 
-    public ArrayList<vCard> getRecipients() {
+    public ArrayList<VCard> getRecipients() {
         return mRecipient;
     }
 
-    public void setRecipient(vCard recipient) {
-        if(this.mRecipient == null)
-            this.mRecipient = new ArrayList<vCard>();
+    public void setRecipient(VCard recipient) {
+        if (this.mRecipient == null) {
+            this.mRecipient = new ArrayList<VCard>();
+        }
         this.mRecipient.add(recipient);
     }
+
     public void addRecipient(String[] btUcis, String[] btUids) {
-        if(mRecipient == null)
-            mRecipient = new ArrayList<vCard>();
-        mRecipient.add(new vCard(null,null,null,null,btUids, btUcis));
+        if (mRecipient == null) {
+            mRecipient = new ArrayList<VCard>();
+        }
+        mRecipient.add(new VCard(null, null, null, null, btUids, btUcis));
     }
-    public void addRecipient(String name, String formattedName,
-                             String[] phoneNumbers,
-                             String[] emailAddresses,
-                             String[] btUids,
-                             String[] btUcis) {
-        if(mRecipient == null)
-            mRecipient = new ArrayList<vCard>();
-        mRecipient.add(new vCard(name, formattedName, phoneNumbers,
-                    emailAddresses,btUids, btUcis));
+
+    public void addRecipient(String name, String formattedName, String[] phoneNumbers,
+            String[] emailAddresses, String[] btUids, String[] btUcis) {
+        if (mRecipient == null) {
+            mRecipient = new ArrayList<VCard>();
+        }
+        mRecipient.add(
+                new VCard(name, formattedName, phoneNumbers, emailAddresses, btUids, btUcis));
     }
 
     public void addRecipient(String name, String[] phoneNumbers, String[] emailAddresses) {
-        if(mRecipient == null)
-            mRecipient = new ArrayList<vCard>();
-        mRecipient.add(new vCard(name, phoneNumbers, emailAddresses));
+        if (mRecipient == null) {
+            mRecipient = new ArrayList<VCard>();
+        }
+        mRecipient.add(new VCard(name, phoneNumbers, emailAddresses));
     }
 
     /**
@@ -906,14 +937,14 @@
      * @return the resulting string.
      */
     protected String encodeBinary(byte[] pduData, byte[] scAddressData) {
-        StringBuilder out = new StringBuilder((pduData.length + scAddressData.length)*2);
-        for(int i = 0; i < scAddressData.length; i++) {
-            out.append(Integer.toString((scAddressData[i] >> 4) & 0x0f,16)); // MS-nibble first
-            out.append(Integer.toString( scAddressData[i]       & 0x0f,16));
+        StringBuilder out = new StringBuilder((pduData.length + scAddressData.length) * 2);
+        for (int i = 0; i < scAddressData.length; i++) {
+            out.append(Integer.toString((scAddressData[i] >> 4) & 0x0f, 16)); // MS-nibble first
+            out.append(Integer.toString(scAddressData[i] & 0x0f, 16));
         }
-        for(int i = 0; i < pduData.length; i++) {
-            out.append(Integer.toString((pduData[i] >> 4) & 0x0f,16)); // MS-nibble first
-            out.append(Integer.toString( pduData[i]       & 0x0f,16));
+        for (int i = 0; i < pduData.length; i++) {
+            out.append(Integer.toString((pduData[i] >> 4) & 0x0f, 16)); // MS-nibble first
+            out.append(Integer.toString(pduData[i] & 0x0f, 16));
             /*out.append(Integer.toHexString(data[i]));*/ /* This is the same as above, but does not
                                                            * include the needed 0's
                                                            * e.g. it converts the value 3 to "3"
@@ -928,28 +959,28 @@
      * @return the byte[] represented in the data.
      */
     protected byte[] decodeBinary(String data) {
-        byte[] out = new byte[data.length()/2];
+        byte[] out = new byte[data.length() / 2];
         String value;
-        if(D) Log.d(TAG,"Decoding binary data: START:" + data + ":END");
-        for(int i = 0, j = 0, n = out.length; i < n; i++)
-        {
+        if (D) {
+            Log.d(TAG, "Decoding binary data: START:" + data + ":END");
+        }
+        for (int i = 0, j = 0, n = out.length; i < n; i++) {
             value = data.substring(j++, ++j);
             // same as data.substring(2*i, 2*i+1+1) - substring() uses end-1 for last index
-            out[i] = (byte)(Integer.valueOf(value, 16) & 0xff);
+            out[i] = (byte) (Integer.valueOf(value, 16) & 0xff);
         }
-        if(D) {
+        if (D) {
             StringBuilder sb = new StringBuilder(out.length);
-            for(int i = 0, n = out.length; i < n; i++)
-            {
-                sb.append(String.format("%02X",out[i] & 0xff));
+            for (int i = 0, n = out.length; i < n; i++) {
+                sb.append(String.format("%02X", out[i] & 0xff));
             }
-            Log.d(TAG,"Decoded binary data: START:" + sb.toString() + ":END");
+            Log.d(TAG, "Decoded binary data: START:" + sb.toString() + ":END");
         }
         return out;
     }
 
-    public byte[] encodeGeneric(ArrayList<byte[]> bodyFragments) throws UnsupportedEncodingException
-    {
+    public byte[] encodeGeneric(ArrayList<byte[]> bodyFragments)
+            throws UnsupportedEncodingException {
         StringBuilder sb = new StringBuilder(256);
         byte[] msgStart, msgEnd;
         sb.append("BEGIN:BMSG").append("\r\n");
@@ -957,34 +988,41 @@
         sb.append(mVersionString).append("\r\n");
         sb.append("STATUS:").append(mStatus).append("\r\n");
         sb.append("TYPE:").append(mType.name()).append("\r\n");
-        if(mFolder.length() > 512)
-            sb.append("FOLDER:").append(
-                    mFolder.substring(mFolder.length()-512, mFolder.length())).append("\r\n");
-        else
+        if (mFolder.length() > 512) {
+            sb.append("FOLDER:")
+                    .append(mFolder.substring(mFolder.length() - 512, mFolder.length()))
+                    .append("\r\n");
+        } else {
             sb.append("FOLDER:").append(mFolder).append("\r\n");
-        if(!mVersionString.contains("1.0")){
+        }
+        if (!mVersionString.contains("1.0")) {
             sb.append("EXTENDEDDATA:").append("\r\n");
         }
-        if(mOriginator != null){
-            for(vCard element : mOriginator)
+        if (mOriginator != null) {
+            for (VCard element : mOriginator) {
                 element.encode(sb);
+            }
         }
         /* If we need the three levels of env. at some point - we do have a level in the
          *  vCards that could be used to determine the levels of the envelope.
          */
 
         sb.append("BEGIN:BENV").append("\r\n");
-        if(mRecipient != null){
-            for(vCard element : mRecipient) {
-                if(V) Log.v(TAG, "encodeGeneric: recipient email" + element.getFirstEmail());
+        if (mRecipient != null) {
+            for (VCard element : mRecipient) {
+                if (V) {
+                    Log.v(TAG, "encodeGeneric: recipient email" + element.getFirstEmail());
+                }
                 element.encode(sb);
             }
         }
         sb.append("BEGIN:BBODY").append("\r\n");
-        if(mEncoding != null && mEncoding != "")
+        if (mEncoding != null && !mEncoding.isEmpty()) {
             sb.append("ENCODING:").append(mEncoding).append("\r\n");
-        if(mCharset != null && mCharset != "")
+        }
+        if (mCharset != null && !mCharset.isEmpty()) {
             sb.append("CHARSET:").append(mCharset).append("\r\n");
+        }
 
 
         int length = 0;
@@ -1006,8 +1044,8 @@
 
         try {
 
-            ByteArrayOutputStream stream = new ByteArrayOutputStream(
-                                                       msgStart.length + msgEnd.length + length);
+            ByteArrayOutputStream stream =
+                    new ByteArrayOutputStream(msgStart.length + msgEnd.length + length);
             stream.write(msgStart);
 
             for (byte[] fragment : bodyFragments) {
@@ -1017,10 +1055,12 @@
             }
             stream.write(msgEnd);
 
-            if(V) Log.v(TAG,stream.toString("UTF-8"));
+            if (V) {
+                Log.v(TAG, stream.toString("UTF-8"));
+            }
             return stream.toByteArray();
         } catch (IOException e) {
-            Log.w(TAG,e);
+            Log.w(TAG, e);
             return null;
         }
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageEmail.java b/src/com/android/bluetooth/map/BluetoothMapbMessageEmail.java
index 83ec646..0a21c44 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageEmail.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageEmail.java
@@ -14,11 +14,11 @@
 */
 package com.android.bluetooth.map;
 
+import android.util.Log;
+
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 
-import android.util.Log;
-
 
 public class BluetoothMapbMessageEmail extends BluetoothMapbMessage {
 
@@ -34,29 +34,34 @@
         return mEmailBody;
     }
 
+    @Override
     public void parseMsgPart(String msgPart) {
-        if (mEmailBody == null)
+        if (mEmailBody == null) {
             mEmailBody = msgPart;
-        else
+        } else {
             mEmailBody += msgPart;
+        }
     }
 
     /**
      * Set initial values before parsing - will be called is a message body is found
      * during parsing.
      */
+    @Override
     public void parseMsgInit() {
         // Not used for e-mail
     }
 
-    public byte[] encode() throws UnsupportedEncodingException
-    {
+    @Override
+    public byte[] encode() throws UnsupportedEncodingException {
         ArrayList<byte[]> bodyFragments = new ArrayList<byte[]>();
 
-        /* Store the messages in an ArrayList to be able to handle the different message types in a generic way.
+        /* Store the messages in an ArrayList to be able to handle the different message types in
+         a generic way.
          * We use byte[] since we need to extract the length in bytes. */
-        if(mEmailBody != null) {
-            String tmpBody = mEmailBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
+        if (mEmailBody != null) {
+            String tmpBody = mEmailBody.replaceAll("END:MSG",
+                    "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
             bodyFragments.add(tmpBody.getBytes("UTF-8"));
         } else {
             Log.e(TAG, "Email has no body - this should not be possible");
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
index 20147b9..a247bff 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
@@ -14,6 +14,11 @@
 */
 package com.android.bluetooth.map;
 
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+import android.util.Base64;
+import android.util.Log;
+
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
 import java.nio.charset.IllegalCharsetNameException;
@@ -24,11 +29,6 @@
 import java.util.Locale;
 import java.util.UUID;
 
-import android.text.util.Rfc822Token;
-import android.text.util.Rfc822Tokenizer;
-import android.util.Base64;
-import android.util.Log;
-
 public class BluetoothMapbMessageMime extends BluetoothMapbMessage {
 
     public static class MimePart {
@@ -51,12 +51,12 @@
             String charset = mCharsetName;
             // Figure out if we support the charset, else fall back to UTF-8, as this is what
             // the MAP specification suggest to use, and is compatible with US-ASCII.
-            if(charset == null){
+            if (charset == null) {
                 charset = "UTF-8";
             } else {
                 charset = charset.toUpperCase();
                 try {
-                    if(Charset.isSupported(charset) == false) {
+                    if (!Charset.isSupported(charset)) {
                         charset = "UTF-8";
                     }
                 } catch (IllegalCharsetNameException e) {
@@ -64,259 +64,309 @@
                     charset = "UTF-8";
                 }
             }
-            try{
+            try {
                 result = new String(mData, charset);
             } catch (UnsupportedEncodingException e) {
                 /* This cannot happen unless Charset.isSupported() is out of sync with String */
-                try{
+                try {
                     result = new String(mData, "UTF-8");
-                } catch (UnsupportedEncodingException e2) {/* This cannot happen */}
+                } catch (UnsupportedEncodingException e2) {
+                    Log.e(TAG, "getDataAsString: " + e);
+                }
             }
             return result;
         }
 
         public void encode(StringBuilder sb, String boundaryTag, boolean last)
-                                                       throws UnsupportedEncodingException {
+                throws UnsupportedEncodingException {
             sb.append("--").append(boundaryTag).append("\r\n");
-            if(mContentType != null)
+            if (mContentType != null) {
                 sb.append("Content-Type: ").append(mContentType);
-            if(mCharsetName != null)
+            }
+            if (mCharsetName != null) {
                 sb.append("; ").append("charset=\"").append(mCharsetName).append("\"");
+            }
             sb.append("\r\n");
-            if(mContentLocation != null)
+            if (mContentLocation != null) {
                 sb.append("Content-Location: ").append(mContentLocation).append("\r\n");
-            if(mContentId != null)
+            }
+            if (mContentId != null) {
                 sb.append("Content-ID: ").append(mContentId).append("\r\n");
-            if(mContentDisposition != null)
+            }
+            if (mContentDisposition != null) {
                 sb.append("Content-Disposition: ").append(mContentDisposition).append("\r\n");
-            if(mData != null) {
-                /* TODO: If errata 4176 is adopted in the current form (it is not in either 1.1 or 1.2),
+            }
+            if (mData != null) {
+                /* TODO: If errata 4176 is adopted in the current form (it is not in either 1.1
+                or 1.2),
                 the below use of UTF-8 is not allowed, Base64 should be used for text. */
 
-                if(mContentType != null &&
-                        (mContentType.toUpperCase().contains("TEXT") ||
-                         mContentType.toUpperCase().contains("SMIL") )) {
-                    String text = new String(mData,"UTF-8");
-                    if(text.getBytes().length == text.getBytes("UTF-8").length){
+                if (mContentType != null && (mContentType.toUpperCase().contains("TEXT")
+                        || mContentType.toUpperCase().contains("SMIL"))) {
+                    String text = new String(mData, "UTF-8");
+                    if (text.getBytes().length == text.getBytes("UTF-8").length) {
                         /* Add the header split empty line */
                         sb.append("Content-Transfer-Encoding: 8BIT\r\n\r\n");
-                    }else {
+                    } else {
                         /* Add the header split empty line */
                         sb.append("Content-Transfer-Encoding: Quoted-Printable\r\n\r\n");
                         text = BluetoothMapUtils.encodeQuotedPrintable(mData);
                     }
                     sb.append(text).append("\r\n");
-                }
-                else {
+                } else {
                     /* Add the header split empty line */
                     sb.append("Content-Transfer-Encoding: Base64\r\n\r\n");
                     sb.append(Base64.encodeToString(mData, Base64.DEFAULT)).append("\r\n");
                 }
             }
-            if(last) {
+            if (last) {
                 sb.append("--").append(boundaryTag).append("--").append("\r\n");
             }
         }
 
         public void encodePlainText(StringBuilder sb) throws UnsupportedEncodingException {
-            if(mContentType != null && mContentType.toUpperCase().contains("TEXT")) {
+            if (mContentType != null && mContentType.toUpperCase().contains("TEXT")) {
                 String text = new String(mData, "UTF-8");
-                if(text.getBytes().length != text.getBytes("UTF-8").length){
-                        text = BluetoothMapUtils.encodeQuotedPrintable(mData);
+                if (text.getBytes().length != text.getBytes("UTF-8").length) {
+                    text = BluetoothMapUtils.encodeQuotedPrintable(mData);
                 }
                 sb.append(text).append("\r\n");
-            } else if(mContentType != null && mContentType.toUpperCase().contains("/SMIL")) {
+            } else if (mContentType != null && mContentType.toUpperCase().contains("/SMIL")) {
                 /* Skip the smil.xml, as no-one knows what it is. */
             } else {
                 /* Not a text part, just print the filename or part name if they exist. */
-                if(mPartName != null)
+                if (mPartName != null) {
                     sb.append("<").append(mPartName).append(">\r\n");
-                else
+                } else {
                     sb.append("<").append("attachment").append(">\r\n");
+                }
             }
         }
     }
 
-    private long date = INVALID_VALUE;
-    private String subject = null;
-    private ArrayList<Rfc822Token> from = null;   // Shall not be empty
-    private ArrayList<Rfc822Token> sender = null;   // Shall not be empty
-    private ArrayList<Rfc822Token> to = null;     // Shall not be empty
-    private ArrayList<Rfc822Token> cc = null;     // Can be empty
-    private ArrayList<Rfc822Token> bcc = null;    // Can be empty
-    private ArrayList<Rfc822Token> replyTo = null;// Can be empty
-    private String messageId = null;
-    private ArrayList<MimePart> parts = null;
-    private String contentType = null;
-    private String boundary = null;
-    private boolean textOnly = false;
-    private boolean includeAttachments;
-    private boolean hasHeaders = false;
-    private String encoding = null;
+    private long mDate = INVALID_VALUE;
+    private String mSubject = null;
+    private ArrayList<Rfc822Token> mFrom = null;   // Shall not be empty
+    private ArrayList<Rfc822Token> mSender = null;   // Shall not be empty
+    private ArrayList<Rfc822Token> mTo = null;     // Shall not be empty
+    private ArrayList<Rfc822Token> mCc = null;     // Can be empty
+    private ArrayList<Rfc822Token> mBcc = null;    // Can be empty
+    private ArrayList<Rfc822Token> mReplyTo = null; // Can be empty
+    private String mMessageId = null;
+    private ArrayList<MimePart> mParts = null;
+    private String mContentType = null;
+    private String mBoundary = null;
+    private boolean mTextonly = false;
+    private boolean mIncludeAttachments;
+    private boolean mHasHeaders = false;
+    private String mMyEncoding = null;
 
     private String getBoundary() {
-        if(boundary == null)
-            // Include "=_" as these cannot occur in quoted printable text
-            boundary = "--=_" + UUID.randomUUID();
-        return boundary;
+        // Include "=_" as these cannot occur in quoted printable text
+        if (mBoundary == null) {
+            mBoundary = "--=_" + UUID.randomUUID();
+        }
+        return mBoundary;
     }
 
     /**
      * @return the parts
      */
     public ArrayList<MimePart> getMimeParts() {
-        return parts;
+        return mParts;
     }
 
     public String getMessageAsText() {
         StringBuilder sb = new StringBuilder();
-        if(subject != null && !subject.isEmpty()) {
-            sb.append("<Sub:").append(subject).append("> ");
+        if (mSubject != null && !mSubject.isEmpty()) {
+            sb.append("<Sub:").append(mSubject).append("> ");
         }
-        if(parts != null) {
-            for(MimePart part : parts) {
-                if(part.mContentType.toUpperCase().contains("TEXT")) {
+        if (mParts != null) {
+            for (MimePart part : mParts) {
+                if (part.mContentType.toUpperCase().contains("TEXT")) {
                     sb.append(new String(part.mData));
                 }
             }
         }
         return sb.toString();
     }
+
     public MimePart addMimePart() {
-        if(parts == null)
-            parts = new ArrayList<BluetoothMapbMessageMime.MimePart>();
+        if (mParts == null) {
+            mParts = new ArrayList<BluetoothMapbMessageMime.MimePart>();
+        }
         MimePart newPart = new MimePart();
-        parts.add(newPart);
+        mParts.add(newPart);
         return newPart;
     }
+
     public String getDateString() {
         SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
-        Date dateObj = new Date(date);
+        Date dateObj = new Date(mDate);
         return format.format(dateObj); // Format according to RFC 2822 page 14
     }
+
     public long getDate() {
-        return date;
+        return mDate;
     }
+
     public void setDate(long date) {
-        this.date = date;
+        this.mDate = date;
     }
+
     public String getSubject() {
-        return subject;
+        return mSubject;
     }
+
     public void setSubject(String subject) {
-        this.subject = subject;
+        this.mSubject = subject;
     }
+
     public ArrayList<Rfc822Token> getFrom() {
-        return from;
+        return mFrom;
     }
+
     public void setFrom(ArrayList<Rfc822Token> from) {
-        this.from = from;
+        this.mFrom = from;
     }
+
     public void addFrom(String name, String address) {
-        if(this.from == null)
-            this.from = new ArrayList<Rfc822Token>(1);
-        this.from.add(new Rfc822Token(name, address, null));
+        if (this.mFrom == null) {
+            this.mFrom = new ArrayList<Rfc822Token>(1);
+        }
+        this.mFrom.add(new Rfc822Token(name, address, null));
     }
+
     public ArrayList<Rfc822Token> getSender() {
-        return sender;
+        return mSender;
     }
+
     public void setSender(ArrayList<Rfc822Token> sender) {
-        this.sender = sender;
+        this.mSender = sender;
     }
+
     public void addSender(String name, String address) {
-        if(this.sender == null)
-            this.sender = new ArrayList<Rfc822Token>(1);
-        this.sender.add(new Rfc822Token(name,address,null));
+        if (this.mSender == null) {
+            this.mSender = new ArrayList<Rfc822Token>(1);
+        }
+        this.mSender.add(new Rfc822Token(name, address, null));
     }
+
     public ArrayList<Rfc822Token> getTo() {
-        return to;
+        return mTo;
     }
+
     public void setTo(ArrayList<Rfc822Token> to) {
-        this.to = to;
+        this.mTo = to;
     }
+
     public void addTo(String name, String address) {
-        if(this.to == null)
-            this.to = new ArrayList<Rfc822Token>(1);
-        this.to.add(new Rfc822Token(name, address, null));
+        if (this.mTo == null) {
+            this.mTo = new ArrayList<Rfc822Token>(1);
+        }
+        this.mTo.add(new Rfc822Token(name, address, null));
     }
+
     public ArrayList<Rfc822Token> getCc() {
-        return cc;
+        return mCc;
     }
+
     public void setCc(ArrayList<Rfc822Token> cc) {
-        this.cc = cc;
+        this.mCc = cc;
     }
+
     public void addCc(String name, String address) {
-        if(this.cc == null)
-            this.cc = new ArrayList<Rfc822Token>(1);
-        this.cc.add(new Rfc822Token(name, address, null));
+        if (this.mCc == null) {
+            this.mCc = new ArrayList<Rfc822Token>(1);
+        }
+        this.mCc.add(new Rfc822Token(name, address, null));
     }
+
     public ArrayList<Rfc822Token> getBcc() {
-        return bcc;
+        return mBcc;
     }
+
     public void setBcc(ArrayList<Rfc822Token> bcc) {
-        this.bcc = bcc;
+        this.mBcc = bcc;
     }
+
     public void addBcc(String name, String address) {
-        if(this.bcc == null)
-            this.bcc = new ArrayList<Rfc822Token>(1);
-        this.bcc.add(new Rfc822Token(name, address, null));
+        if (this.mBcc == null) {
+            this.mBcc = new ArrayList<Rfc822Token>(1);
+        }
+        this.mBcc.add(new Rfc822Token(name, address, null));
     }
+
     public ArrayList<Rfc822Token> getReplyTo() {
-        return replyTo;
+        return mReplyTo;
     }
+
     public void setReplyTo(ArrayList<Rfc822Token> replyTo) {
-        this.replyTo = replyTo;
+        this.mReplyTo = replyTo;
     }
+
     public void addReplyTo(String name, String address) {
-        if(this.replyTo == null)
-            this.replyTo = new ArrayList<Rfc822Token>(1);
-        this.replyTo.add(new Rfc822Token(name, address, null));
+        if (this.mReplyTo == null) {
+            this.mReplyTo = new ArrayList<Rfc822Token>(1);
+        }
+        this.mReplyTo.add(new Rfc822Token(name, address, null));
     }
+
     public void setMessageId(String messageId) {
-        this.messageId = messageId;
+        this.mMessageId = messageId;
     }
+
     public String getMessageId() {
-        return messageId;
+        return mMessageId;
     }
+
     public void setContentType(String contentType) {
-        this.contentType = contentType;
+        this.mContentType = contentType;
     }
+
     public String getContentType() {
-        return contentType;
+        return mContentType;
     }
+
     public void setTextOnly(boolean textOnly) {
-        this.textOnly = textOnly;
+        this.mTextonly = textOnly;
     }
+
     public boolean getTextOnly() {
-        return textOnly;
+        return mTextonly;
     }
+
     public void setIncludeAttachments(boolean includeAttachments) {
-        this.includeAttachments = includeAttachments;
+        this.mIncludeAttachments = includeAttachments;
     }
+
     public boolean getIncludeAttachments() {
-        return includeAttachments;
+        return mIncludeAttachments;
     }
+
     public void updateCharset() {
-        if(parts != null) {
+        if (mParts != null) {
             mCharset = null;
-            for(MimePart part : parts) {
-                if(part.mContentType != null &&
-                   part.mContentType.toUpperCase().contains("TEXT")) {
+            for (MimePart part : mParts) {
+                if (part.mContentType != null && part.mContentType.toUpperCase().contains("TEXT")) {
                     mCharset = "UTF-8";
-                    if(V) Log.v(TAG,"Charset set to UTF-8");
+                    if (V) {
+                        Log.v(TAG, "Charset set to UTF-8");
+                    }
                     break;
                 }
             }
         }
     }
+
     public int getSize() {
-        int message_size = 0;
-        if(parts != null) {
-            for(MimePart part : parts) {
-                message_size += part.mData.length;
+        int messageSize = 0;
+        if (mParts != null) {
+            for (MimePart part : mParts) {
+                messageSize += part.mData.length;
             }
         }
-        return message_size;
+        return messageSize;
     }
 
     /**
@@ -335,11 +385,10 @@
         int partLength, lineLength = 0;
         lineLength += headerName.getBytes().length;
         sb.append(headerName);
-        for(Rfc822Token address : addresses) {
-            partLength = address.toString().getBytes().length+1;
+        for (Rfc822Token address : addresses) {
+            partLength = address.toString().getBytes().length + 1;
             // Add folding if needed
-            if(lineLength + partLength >= 998) // max line length in RFC2822
-            {
+            if (lineLength + partLength >= 998 /* max line length in RFC2822 */) {
                 sb.append("\r\n "); // Append a FWS (folding whitespace)
                 lineLength = 0;
             }
@@ -349,8 +398,7 @@
         sb.append("\r\n");
     }
 
-    public void encodeHeaders(StringBuilder sb) throws UnsupportedEncodingException
-    {
+    public void encodeHeaders(StringBuilder sb) throws UnsupportedEncodingException {
         /* TODO: From RFC-4356 - about the RFC-(2)822 headers:
          *    "Current Internet Message format requires that only 7-bit US-ASCII
          *     characters be present in headers.  Non-7-bit characters in an address
@@ -360,8 +408,9 @@
          *     according to [Hdr-Enc]."
          *    We need to add the address encoding in encodeHeaderAddresses, but it is not
          *    straight forward, as it is unclear how to do this.  */
-        if (date != INVALID_VALUE)
+        if (mDate != INVALID_VALUE) {
             sb.append("Date: ").append(getDateString()).append("\r\n");
+        }
         /* According to RFC-2822 headers must use US-ASCII, where the MAP specification states
          * UTF-8 should be used for the entire <bmessage-body-content>. We let the MAP specification
          * take precedence above the RFC-2822.
@@ -375,36 +424,49 @@
             sb.append(Base64.encodeToString(subject.getBytes("utf-8"), Base64.DEFAULT));
             sb.append("?=\r\n");
         }*/
-        if (subject != null)
-            sb.append("Subject: ").append(subject).append("\r\n");
-        if(from == null)
+        if (mSubject != null) {
+            sb.append("Subject: ").append(mSubject).append("\r\n");
+        }
+        if (mFrom == null) {
             sb.append("From: \r\n");
-        if(from != null)
-            encodeHeaderAddresses(sb, "From: ", from); // This includes folding if needed.
-        if(sender != null)
-            encodeHeaderAddresses(sb, "Sender: ", sender); // This includes folding if needed.
+        }
+        if (mFrom != null) {
+            encodeHeaderAddresses(sb, "From: ", mFrom); // This includes folding if needed.
+        }
+        if (mSender != null) {
+            encodeHeaderAddresses(sb, "Sender: ", mSender); // This includes folding if needed.
+        }
         /* For MMS one recipient(to, cc or bcc) must exists, if none: 'To:  undisclosed-
          * recipients:;' could be used.
          */
-        if(to == null && cc == null && bcc == null)
+        if (mTo == null && mCc == null && mBcc == null) {
             sb.append("To:  undisclosed-recipients:;\r\n");
-        if(to != null)
-            encodeHeaderAddresses(sb, "To: ", to); // This includes folding if needed.
-        if(cc != null)
-            encodeHeaderAddresses(sb, "Cc: ", cc); // This includes folding if needed.
-        if(bcc != null)
-            encodeHeaderAddresses(sb, "Bcc: ", bcc); // This includes folding if needed.
-        if(replyTo != null)
-            encodeHeaderAddresses(sb, "Reply-To: ", replyTo); // This includes folding if needed.
-        if(includeAttachments == true)
-        {
-            if(messageId != null)
-                sb.append("Message-Id: ").append(messageId).append("\r\n");
-            if(contentType != null)
-                sb.append("Content-Type: ").append(
-                        contentType).append("; boundary=").append(getBoundary()).append("\r\n");
         }
-     // If no headers exists, we still need two CRLF, hence keep it out of the if above.
+        if (mTo != null) {
+            encodeHeaderAddresses(sb, "To: ", mTo); // This includes folding if needed.
+        }
+        if (mCc != null) {
+            encodeHeaderAddresses(sb, "Cc: ", mCc); // This includes folding if needed.
+        }
+        if (mBcc != null) {
+            encodeHeaderAddresses(sb, "Bcc: ", mBcc); // This includes folding if needed.
+        }
+        if (mReplyTo != null) {
+            encodeHeaderAddresses(sb, "Reply-To: ", mReplyTo); // This includes folding if needed.
+        }
+        if (mIncludeAttachments) {
+            if (mMessageId != null) {
+                sb.append("Message-Id: ").append(mMessageId).append("\r\n");
+            }
+            if (mContentType != null) {
+                sb.append("Content-Type: ")
+                        .append(mContentType)
+                        .append("; boundary=")
+                        .append(getBoundary())
+                        .append("\r\n");
+            }
+        }
+        // If no headers exists, we still need two CRLF, hence keep it out of the if above.
         sb.append("\r\n");
     }
 
@@ -442,35 +504,34 @@
      * @return
      * @throws UnsupportedEncodingException
      */
-    public byte[] encodeMime() throws UnsupportedEncodingException
-    {
+    public byte[] encodeMime() throws UnsupportedEncodingException {
         ArrayList<byte[]> bodyFragments = new ArrayList<byte[]>();
         StringBuilder sb = new StringBuilder();
         int count = 0;
         String mimeBody;
 
-        encoding = "8BIT"; // The encoding used
+        mEncoding = "8BIT"; // The encoding used
 
         encodeHeaders(sb);
-        if(parts != null) {
-            if(getIncludeAttachments() == false) {
-                for(MimePart part : parts) {
+        if (mParts != null) {
+            if (!getIncludeAttachments()) {
+                for (MimePart part : mParts) {
                     /* We call encode on all parts, to include a tag,
                      * where an attachment is missing. */
                     part.encodePlainText(sb);
                 }
             } else {
-                for(MimePart part : parts) {
+                for (MimePart part : mParts) {
                     count++;
-                    part.encode(sb, getBoundary(), (count == parts.size()));
+                    part.encode(sb, getBoundary(), (count == mParts.size()));
                 }
             }
         }
 
         mimeBody = sb.toString();
 
-        if(mimeBody != null) {
-           // Replace any occurrences of END:MSG with \END:MSG
+        if (mimeBody != null) {
+            // Replace any occurrences of END:MSG with \END:MSG
             String tmpBody = mimeBody.replaceAll("END:MSG", "/END\\:MSG");
             bodyFragments.add(tmpBody.getBytes("UTF-8"));
         } else {
@@ -489,26 +550,32 @@
      */
     private String parseMimeHeaders(String hdrPart) {
         String[] headers = hdrPart.split("\r\n");
-        if(D) Log.d(TAG,"Header count=" + headers.length);
+        if (D) {
+            Log.d(TAG, "Header count=" + headers.length);
+        }
         String header;
-        hasHeaders = false;
+        mHasHeaders = false;
 
-        for(int i = 0, c = headers.length; i < c; i++) {
+        for (int i = 0, c = headers.length; i < c; i++) {
             header = headers[i];
-            if(D) Log.d(TAG,"Header[" + i + "]: " + header);
+            if (D) {
+                Log.d(TAG, "Header[" + i + "]: " + header);
+            }
             /* We need to figure out if any headers are present, in cases where devices do
              * not follow the e-mail RFCs.
              * Skip empty lines, and then parse headers until a non-header line is found,
              * at which point we treat the remaining as plain text.
              */
-            if(header.trim() == "")
+            if (header.trim().isEmpty()) {
                 continue;
-            String[] headerParts = header.split(":",2);
-            if(headerParts.length != 2) {
+            }
+            String[] headerParts = header.split(":", 2);
+            if (headerParts.length != 2) {
                 // We treat the remaining content as plain text.
                 StringBuilder remaining = new StringBuilder();
-                for(; i < c; i++)
+                for (; i < c; i++) {
                     remaining.append(headers[i]);
+                }
 
                 return remaining.toString();
             }
@@ -520,56 +587,60 @@
             /* If this is empty, the MSE needs to fill it in before sending the message.
              * This happens when sending the MMS.
              */
-            if(headerType.contains("FROM")) {
+            if (headerType.contains("FROM")) {
                 headerValue = BluetoothMapUtils.stripEncoding(headerValue);
-                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
-                from = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
-            } else if(headerType.contains("TO")) {
+                Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(headerValue);
+                mFrom = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            } else if (headerType.contains("TO")) {
                 headerValue = BluetoothMapUtils.stripEncoding(headerValue);
-                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
-                to = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
-            } else if(headerType.contains("CC")) {
+                Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(headerValue);
+                mTo = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            } else if (headerType.contains("CC")) {
                 headerValue = BluetoothMapUtils.stripEncoding(headerValue);
-                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
-                cc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
-            } else if(headerType.contains("BCC")) {
+                Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(headerValue);
+                mCc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            } else if (headerType.contains("BCC")) {
                 headerValue = BluetoothMapUtils.stripEncoding(headerValue);
-                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
-                bcc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
-            } else if(headerType.contains("REPLY-TO")) {
+                Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(headerValue);
+                mBcc = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            } else if (headerType.contains("REPLY-TO")) {
                 headerValue = BluetoothMapUtils.stripEncoding(headerValue);
-                Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue);
-                replyTo = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
-            } else if(headerType.contains("SUBJECT")) { // Other headers
-                subject = BluetoothMapUtils.stripEncoding(headerValue);
-            } else if(headerType.contains("MESSAGE-ID")) {
-                messageId = headerValue;
-            } else if(headerType.contains("DATE")) {
+                Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(headerValue);
+                mReplyTo = new ArrayList<Rfc822Token>(Arrays.asList(tokens));
+            } else if (headerType.contains("SUBJECT")) { // Other headers
+                mSubject = BluetoothMapUtils.stripEncoding(headerValue);
+            } else if (headerType.contains("MESSAGE-ID")) {
+                mMessageId = headerValue;
+            } else if (headerType.contains("DATE")) {
                 /* The date is not needed, as the time stamp will be set in the DB
                  * when the message is send. */
-            } else if(headerType.contains("MIME-VERSION")) {
+            } else if (headerType.contains("MIME-VERSION")) {
                 /* The mime version is not needed */
-            } else if(headerType.contains("CONTENT-TYPE")) {
+            } else if (headerType.contains("CONTENT-TYPE")) {
                 String[] contentTypeParts = headerValue.split(";");
-                contentType = contentTypeParts[0];
+                mContentType = contentTypeParts[0];
                 // Extract the boundary if it exists
-                for(int j=1, n=contentTypeParts.length; j<n; j++)
-                {
-                    if(contentTypeParts[j].contains("boundary")) {
-                        boundary = contentTypeParts[j].split("boundary[\\s]*=", 2)[1].trim();
+                for (int j = 1, n = contentTypeParts.length; j < n; j++) {
+                    if (contentTypeParts[j].contains("boundary")) {
+                        mBoundary = contentTypeParts[j].split("boundary[\\s]*=", 2)[1].trim();
                         // removing quotes from boundary string
-                        if ((boundary.charAt(0) == '\"')
-                                && (boundary.charAt(boundary.length()-1) == '\"'))
-                            boundary = boundary.substring(1, boundary.length()-1);
-                        if(D) Log.d(TAG,"Boundary tag=" + boundary);
-                    } else if(contentTypeParts[j].contains("charset")) {
+                        if ((mBoundary.charAt(0) == '\"') && (
+                                mBoundary.charAt(mBoundary.length() - 1) == '\"')) {
+                            mBoundary = mBoundary.substring(1, mBoundary.length() - 1);
+                        }
+                        if (D) {
+                            Log.d(TAG, "Boundary tag=" + mBoundary);
+                        }
+                    } else if (contentTypeParts[j].contains("charset")) {
                         mCharset = contentTypeParts[j].split("charset[\\s]*=", 2)[1].trim();
                     }
                 }
-            } else if(headerType.contains("CONTENT-TRANSFER-ENCODING")) {
-                encoding = headerValue;
+            } else if (headerType.contains("CONTENT-TRANSFER-ENCODING")) {
+                mMyEncoding = headerValue;
             } else {
-                if(D) Log.w(TAG,"Skipping unknown header: " + headerType + " (" + header + ")");
+                if (D) {
+                    Log.w(TAG, "Skipping unknown header: " + headerType + " (" + header + ")");
+                }
             }
         }
         return null;
@@ -578,68 +649,70 @@
     private void parseMimePart(String partStr) {
         String[] parts = partStr.split("\r\n\r\n", 2); // Split the header from the body
         MimePart newPart = addMimePart();
-        String partEncoding = encoding; /* Use the overall encoding as default */
+        String partEncoding = mMyEncoding; /* Use the overall encoding as default */
         String body;
 
         String[] headers = parts[0].split("\r\n");
-        if(D) Log.d(TAG, "parseMimePart: headers count=" + headers.length);
+        if (D) {
+            Log.d(TAG, "parseMimePart: headers count=" + headers.length);
+        }
 
-        if(parts.length != 2) {
+        if (parts.length != 2) {
             body = partStr;
         } else {
-            for(String header : headers) {
+            for (String header : headers) {
                 // Skip empty lines(the \r\n after the boundary tag) and endBoundary tags
-                if((header.length() == 0)
-                        || (header.trim().isEmpty())
-                        || header.trim().equals("--"))
-                    continue;
-
-                String[] headerParts = header.split(":",2);
-                if(headerParts.length != 2) {
-                    if(D) Log.w(TAG, "part-Header not formatted correctly: ");
+                if ((header.length() == 0) || (header.trim().isEmpty()) || header.trim()
+                        .equals("--")) {
                     continue;
                 }
-                if(D) Log.d(TAG, "parseMimePart: header=" + header);
+
+                String[] headerParts = header.split(":", 2);
+                if (headerParts.length != 2) {
+                    if (D) {
+                        Log.w(TAG, "part-Header not formatted correctly: ");
+                    }
+                    continue;
+                }
+                if (D) {
+                    Log.d(TAG, "parseMimePart: header=" + header);
+                }
                 String headerType = headerParts[0].toUpperCase();
                 String headerValue = headerParts[1].trim();
-                if(headerType.contains("CONTENT-TYPE")) {
+                if (headerType.contains("CONTENT-TYPE")) {
                     String[] contentTypeParts = headerValue.split(";");
                     newPart.mContentType = contentTypeParts[0];
                     // Extract the boundary if it exists
-                    for(int j=1, n=contentTypeParts.length; j<n; j++)
-                    {
+                    for (int j = 1, n = contentTypeParts.length; j < n; j++) {
                         String value = contentTypeParts[j].toLowerCase();
-                        if(value.contains("charset")) {
+                        if (value.contains("charset")) {
                             newPart.mCharsetName = value.split("charset[\\s]*=", 2)[1].trim();
                         }
                     }
-                }
-                else if(headerType.contains("CONTENT-LOCATION")) {
+                } else if (headerType.contains("CONTENT-LOCATION")) {
                     // This is used if the smil refers to a file name in its src
                     newPart.mContentLocation = headerValue;
                     newPart.mPartName = headerValue;
-                }
-                else if(headerType.contains("CONTENT-TRANSFER-ENCODING")) {
+                } else if (headerType.contains("CONTENT-TRANSFER-ENCODING")) {
                     partEncoding = headerValue;
-                }
-                else if(headerType.contains("CONTENT-ID")) {
+                } else if (headerType.contains("CONTENT-ID")) {
                     // This is used if the smil refers to a cid:<xxx> in it's src
                     newPart.mContentId = headerValue;
-                }
-                else if(headerType.contains("CONTENT-DISPOSITION")) {
+                } else if (headerType.contains("CONTENT-DISPOSITION")) {
                     // This is used if the smil refers to a cid:<xxx> in it's src
                     newPart.mContentDisposition = headerValue;
-                }
-                else {
-                    if(D) Log.w(TAG,"Skipping unknown part-header: " + headerType
-                                                                     + " (" + header + ")");
+                } else {
+                    if (D) {
+                        Log.w(TAG, "Skipping unknown part-header: " + headerType + " (" + header
+                                + ")");
+                    }
                 }
             }
             body = parts[1];
-            if(body.length() > 2) {
-                if(body.charAt(body.length()-2) == '\r'
-                        && body.charAt(body.length()-2) == '\n') {
-                    body = body.substring(0, body.length()-2);
+            if (body.length() > 2) {
+                if (body.charAt(body.length() - 2) == '\r'
+                        && body.charAt(body.length() - 2) == '\n') {
+                    body = body.substring(0, body.length() - 2);
                 }
             }
         }
@@ -650,15 +723,15 @@
     private void parseMimeBody(String body) {
         MimePart newPart = addMimePart();
         newPart.mCharsetName = mCharset;
-        newPart.mData = decodeBody(body, encoding, mCharset);
+        newPart.mData = decodeBody(body, mMyEncoding, mCharset);
     }
 
     private byte[] decodeBody(String body, String encoding, String charset) {
-        if(encoding != null && encoding.toUpperCase().contains("BASE64")) {
+        if (encoding != null && encoding.toUpperCase().contains("BASE64")) {
             return Base64.decode(body, Base64.DEFAULT);
-        } else if(encoding != null && encoding.toUpperCase().contains("QUOTED-PRINTABLE")) {
+        } else if (encoding != null && encoding.toUpperCase().contains("QUOTED-PRINTABLE")) {
             return BluetoothMapUtils.quotedPrintableToUtf8(body, charset);
-        }else{
+        } else {
             // TODO: handle other encoding types? - here we simply store the string data as bytes
             try {
 
@@ -684,40 +757,41 @@
         String messageBody = null;
         message = message.replaceAll("\\r\\n[ \\\t]+", ""); // Unfold
         messageParts = message.split("\r\n\r\n", 2); // Split the header from the body
-        if(messageParts.length != 2) {
+        if (messageParts.length != 2) {
             // Handle entire message as plain text
             messageBody = message;
-        }
-        else
-        {
+        } else {
             remaining = parseMimeHeaders(messageParts[0]);
             // If we have some text not being a header, add it to the message body.
-            if(remaining != null) {
+            if (remaining != null) {
                 messageBody = remaining + messageParts[1];
-                if(D) Log.d(TAG, "parseMime remaining=" + remaining );
+                if (D) {
+                    Log.d(TAG, "parseMime remaining=" + remaining);
+                }
             } else {
                 messageBody = messageParts[1];
             }
         }
 
-        if(boundary == null)
-        {
+        if (mBoundary == null) {
             // If the boundary is not set, handle as non-multi-part
             parseMimeBody(messageBody);
             setTextOnly(true);
-            if(contentType == null)
-                contentType = "text/plain";
-            parts.get(0).mContentType = contentType;
-        }
-        else
-        {
-            mimeParts = messageBody.split("--" + boundary);
-            if(D) Log.d(TAG, "mimePart count=" + mimeParts.length);
+            if (mContentType == null) {
+                mContentType = "text/plain";
+            }
+            mParts.get(0).mContentType = mContentType;
+        } else {
+            mimeParts = messageBody.split("--" + mBoundary);
+            if (D) {
+                Log.d(TAG, "mimePart count=" + mimeParts.length);
+            }
             // Part 0 is the message to clients not capable of decoding MIME
-            for(int i = 1; i < mimeParts.length - 1; i++) {
+            for (int i = 1; i < mimeParts.length - 1; i++) {
                 String part = mimeParts[i];
-                if (part != null && (part.length() > 0))
+                if (part != null && (part.length() > 0)) {
                     parseMimePart(part);
+                }
             }
         }
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
index 9f57f60..c8628c3 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java
@@ -14,14 +14,15 @@
 */
 package com.android.bluetooth.map;
 
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-
 import android.util.Log;
 
+import com.android.bluetooth.DeviceWorkArounds;
 import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
 public class BluetoothMapbMessageSms extends BluetoothMapbMessage {
 
     private ArrayList<SmsPdu> mSmsBodyPdus = null;
@@ -30,8 +31,9 @@
     public void setSmsBodyPdus(ArrayList<SmsPdu> smsBodyPdus) {
         this.mSmsBodyPdus = smsBodyPdus;
         this.mCharset = null;
-        if(smsBodyPdus.size() > 0)
+        if (smsBodyPdus.size() > 0) {
             this.mEncoding = smsBodyPdus.get(0).getEncodingString();
+        }
     }
 
     public String getSmsBody() {
@@ -46,42 +48,68 @@
 
     @Override
     public void parseMsgPart(String msgPart) {
-        if(mAppParamCharset == BluetoothMapAppParams.CHARSET_NATIVE) {
-            if(D) Log.d(TAG, "Decoding \"" + msgPart + "\" as native PDU");
+        if (mAppParamCharset == BluetoothMapAppParams.CHARSET_NATIVE) {
+            if (D) {
+                Log.d(TAG, "Decoding \"" + msgPart + "\" as native PDU");
+            }
             byte[] msgBytes = decodeBinary(msgPart);
-            if(msgBytes.length > 0 &&
-                    msgBytes[0] < msgBytes.length-1 &&
-                    (msgBytes[msgBytes[0]+1] & 0x03) != 0x01) {
-                if(D) Log.d(TAG, "Only submit PDUs are supported");
+            if (msgBytes.length > 0 && msgBytes[0] < msgBytes.length - 1
+                    && (msgBytes[msgBytes[0] + 1] & 0x03) != 0x01) {
+                if (D) {
+                    Log.d(TAG, "Only submit PDUs are supported");
+                }
                 throw new IllegalArgumentException("Only submit PDUs are supported");
             }
 
             mSmsBody += BluetoothMapSmsPdu.decodePdu(msgBytes,
                     mType == TYPE.SMS_CDMA ? BluetoothMapSmsPdu.SMS_TYPE_CDMA
-                                          : BluetoothMapSmsPdu.SMS_TYPE_GSM);
+                            : BluetoothMapSmsPdu.SMS_TYPE_GSM);
         } else {
             mSmsBody += msgPart;
         }
     }
+
     @Override
     public void parseMsgInit() {
         mSmsBody = "";
     }
 
-    public byte[] encode() throws UnsupportedEncodingException
-    {
+    @Override
+    public byte[] encode() throws UnsupportedEncodingException {
         ArrayList<byte[]> bodyFragments = new ArrayList<byte[]>();
 
-        /* Store the messages in an ArrayList to be able to handle the different message types in a generic way.
+        /* Store the messages in an ArrayList to be able to handle the different message types in
+         a generic way.
          * We use byte[] since we need to extract the length in bytes.
          */
-        if(mSmsBody != null) {
-            String tmpBody = mSmsBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
+        if (mSmsBody != null) {
+            String tmpBody = mSmsBody.replaceAll("END:MSG",
+                    "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG
+            String remoteAddress = BluetoothMapService.getRemoteDevice().getAddress();
+            /* Fix IOT issue with PCM carkit where carkit is unable to parse
+               message if carriage return is present in it */
+            if (DeviceWorkArounds.addressStartsWith(remoteAddress, DeviceWorkArounds.PCM_CARKIT)) {
+                tmpBody = tmpBody.replaceAll("\r", "");
+                /* Fix Message Display issue with FORD SYNC carkit -
+                 * Remove line feed and include only carriage return */
+            } else if (DeviceWorkArounds.addressStartsWith(
+                               remoteAddress, DeviceWorkArounds.FORD_SYNC_CARKIT)) {
+                tmpBody = tmpBody.replaceAll("\n", "");
+                /* Fix IOT issue with SYNC carkit to remove trailing line feeds in the message body
+                 */
+            } else if (DeviceWorkArounds.addressStartsWith(
+                               remoteAddress, DeviceWorkArounds.SYNC_CARKIT)
+                    && tmpBody.length() > 0) {
+                int trailingLF = 0;
+                while ((tmpBody.charAt(tmpBody.length() - trailingLF - 1)) == '\n') trailingLF++;
+                tmpBody = tmpBody.substring(0, (tmpBody.length() - trailingLF));
+            }
             bodyFragments.add(tmpBody.getBytes("UTF-8"));
-        }else if (mSmsBodyPdus != null && mSmsBodyPdus.size() > 0) {
+        } else if (mSmsBodyPdus != null && mSmsBodyPdus.size() > 0) {
             for (SmsPdu pdu : mSmsBodyPdus) {
                 // This cannot(must not) contain END:MSG
-                bodyFragments.add(encodeBinary(pdu.getData(),pdu.getScAddress()).getBytes("UTF-8"));
+                bodyFragments.add(
+                        encodeBinary(pdu.getData(), pdu.getScAddress()).getBytes("UTF-8"));
             }
         } else {
             bodyFragments.add(new byte[0]); // An empty message - no text
diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
index 2558641..eba6485 100644
--- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
+++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java
@@ -67,15 +67,15 @@
     public static final int MSG_MNS_SDP_SEARCH_REGISTRATION = 3;
 
     //Copy SdpManager.SDP_INTENT_DELAY - The timeout to wait for reply from native.
-    private final int MNS_SDP_SEARCH_DELAY = 6000;
+    private static final int MNS_SDP_SEARCH_DELAY = 6000;
     public MnsSdpSearchInfo mMnsLstRegRqst = null;
     private static final int MNS_NOTIFICATION_DELAY = 10;
     public static final ParcelUuid BLUETOOTH_UUID_OBEX_MNS =
             ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
 
 
-    public BluetoothMnsObexClient(BluetoothDevice remoteDevice,
-            SdpMnsRecord mnsRecord, Handler callback) {
+    public BluetoothMnsObexClient(BluetoothDevice remoteDevice, SdpMnsRecord mnsRecord,
+            Handler callback) {
         if (remoteDevice == null) {
             throw new NullPointerException("Obex transport is null");
         }
@@ -95,22 +95,22 @@
     }
 
     class MnsSdpSearchInfo {
-        private boolean isSearchInProgress;
-        int lastMasId;
-        int lastNotificationStatus;
+        private boolean mIsSearchInProgress;
+        public int lastMasId;
+        public int lastNotificationStatus;
 
-        MnsSdpSearchInfo (boolean isSearchON, int masId, int notification) {
-            isSearchInProgress = isSearchON;
+        MnsSdpSearchInfo(boolean isSearchON, int masId, int notification) {
+            mIsSearchInProgress = isSearchON;
             lastMasId = masId;
             lastNotificationStatus = notification;
         }
 
         public boolean isSearchInProgress() {
-            return isSearchInProgress;
+            return mIsSearchInProgress;
         }
 
         public void setIsSearchInProgress(boolean isSearchON) {
-            isSearchInProgress = isSearchON;
+            mIsSearchInProgress = isSearchON;
         }
     }
 
@@ -122,32 +122,38 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case MSG_MNS_NOTIFICATION_REGISTRATION:
-                if (V) Log.v(TAG, "Reg  masId:  " + msg.arg1 + " notfStatus: " + msg.arg2);
-                if (isValidMnsRecord()) {
-                    handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/);
-                } else {
-                    //Should not happen
-                    if (D) Log.d(TAG, "MNS SDP info not available yet - Cannot Connect.");
-                }
-                break;
-            case MSG_MNS_SEND_EVENT:
-                sendEventHandler((byte[])msg.obj/*byte[]*/, msg.arg1 /*masId*/);
-                break;
-            case MSG_MNS_SDP_SEARCH_REGISTRATION:
-                //Initiate SDP Search
-                notifyMnsSdpSearch();
-                //Save the mns search info
-                mMnsLstRegRqst = new MnsSdpSearchInfo(true, msg.arg1, msg.arg2);
-                //Handle notification registration.
-                Message msgReg =
-                        mHandler.obtainMessage(MSG_MNS_NOTIFICATION_REGISTRATION,
-                                msg.arg1, msg.arg2);
-                if (V) Log.v(TAG, "SearchReg  masId:  " + msg.arg1 + " notfStatus: " + msg.arg2);
-                mHandler.sendMessageDelayed(msgReg, MNS_SDP_SEARCH_DELAY);
-                break;
-            default:
-                break;
+                case MSG_MNS_NOTIFICATION_REGISTRATION:
+                    if (V) {
+                        Log.v(TAG, "Reg  masId:  " + msg.arg1 + " notfStatus: " + msg.arg2);
+                    }
+                    if (isValidMnsRecord()) {
+                        handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/);
+                    } else {
+                        //Should not happen
+                        if (D) {
+                            Log.d(TAG, "MNS SDP info not available yet - Cannot Connect.");
+                        }
+                    }
+                    break;
+                case MSG_MNS_SEND_EVENT:
+                    sendEventHandler((byte[]) msg.obj/*byte[]*/, msg.arg1 /*masId*/);
+                    break;
+                case MSG_MNS_SDP_SEARCH_REGISTRATION:
+                    //Initiate SDP Search
+                    notifyMnsSdpSearch();
+                    //Save the mns search info
+                    mMnsLstRegRqst = new MnsSdpSearchInfo(true, msg.arg1, msg.arg2);
+                    //Handle notification registration.
+                    Message msgReg =
+                            mHandler.obtainMessage(MSG_MNS_NOTIFICATION_REGISTRATION, msg.arg1,
+                                    msg.arg2);
+                    if (V) {
+                        Log.v(TAG, "SearchReg  masId:  " + msg.arg1 + " notfStatus: " + msg.arg2);
+                    }
+                    mHandler.sendMessageDelayed(msgReg, MNS_SDP_SEARCH_DELAY);
+                    break;
+                default:
+                    break;
             }
         }
     }
@@ -164,28 +170,38 @@
         try {
             if (mClientSession != null) {
                 mClientSession.disconnect(null);
-                if (D) Log.d(TAG, "OBEX session disconnected");
+                if (D) {
+                    Log.d(TAG, "OBEX session disconnected");
+                }
             }
         } catch (IOException e) {
             Log.w(TAG, "OBEX session disconnect error " + e.getMessage());
         }
         try {
             if (mClientSession != null) {
-                if (D) Log.d(TAG, "OBEX session close mClientSession");
+                if (D) {
+                    Log.d(TAG, "OBEX session close mClientSession");
+                }
                 mClientSession.close();
                 mClientSession = null;
-                if (D) Log.d(TAG, "OBEX session closed");
+                if (D) {
+                    Log.d(TAG, "OBEX session closed");
+                }
             }
         } catch (IOException e) {
             Log.w(TAG, "OBEX session close error:" + e.getMessage());
         }
         if (mTransport != null) {
             try {
-                if (D) Log.d(TAG, "Close Obex Transport");
+                if (D) {
+                    Log.d(TAG, "Close Obex Transport");
+                }
                 mTransport.close();
                 mTransport = null;
                 mConnected = false;
-                if (D) Log.d(TAG, "Obex Transport Closed");
+                if (D) {
+                    Log.d(TAG, "Obex Transport Closed");
+                }
             } catch (IOException e) {
                 Log.e(TAG, "mTransport.close error: " + e.getMessage());
             }
@@ -220,12 +236,14 @@
      * @param masId
      * @param notificationStatus
      */
-    public synchronized void handleRegistration(int masId, int notificationStatus){
-        if(D) Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
+    public synchronized void handleRegistration(int masId, int notificationStatus) {
+        if (D) {
+            Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
+        }
         boolean sendObserverRegistration = true;
         if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
             mRegisteredMasIds.delete(masId);
-            if (mMnsLstRegRqst != null &&  mMnsLstRegRqst.lastMasId == masId) {
+            if (mMnsLstRegRqst != null && mMnsLstRegRqst.lastMasId == masId) {
                 //Clear last saved MNSSdpSearchInfo , if Disconnect requested for same MasId.
                 mMnsLstRegRqst = null;
             }
@@ -233,8 +251,10 @@
             /* Connect if we do not have a connection, and start the content observers providing
              * this thread as Handler.
              */
-            if (isConnected() == false) {
-                if(D) Log.d(TAG, "handleRegistration: connect");
+            if (!isConnected()) {
+                if (D) {
+                    Log.d(TAG, "handleRegistration: connect");
+                }
                 connect();
             }
             sendObserverRegistration = isConnected();
@@ -246,12 +266,16 @@
 
         if (mRegisteredMasIds.size() == 0) {
             // No more registrations - disconnect
-            if(D) Log.d(TAG, "handleRegistration: disconnect");
+            if (D) {
+                Log.d(TAG, "handleRegistration: disconnect");
+            }
             disconnect();
         }
 
         //Register ContentObserver After connect/disconnect MNS channel.
-        if (V) Log.v(TAG, "Send  registerObserver: " + sendObserverRegistration);
+        if (V) {
+            Log.v(TAG, "Send  registerObserver: " + sendObserverRegistration);
+        }
         if (mCallback != null && sendObserverRegistration) {
             Message msg = Message.obtain(mCallback);
             msg.what = BluetoothMapService.MSG_OBSERVER_REGISTRATION;
@@ -266,9 +290,11 @@
     }
 
     public void setMnsRecord(SdpMnsRecord mnsRecord) {
-        if (V) Log.v(TAG, "setMNSRecord");
+        if (V) {
+            Log.v(TAG, "setMNSRecord");
+        }
         if (isValidMnsRecord()) {
-           Log.w(TAG,"MNS Record already available. Still update.");
+            Log.w(TAG, "MNS Record already available. Still update.");
         }
         mMnsRecord = mnsRecord;
         if (mMnsLstRegRqst != null) {
@@ -282,19 +308,24 @@
                     // Clear saved info.
                     mMnsLstRegRqst = null;
                 } else {
-                    if (V) Log.v(TAG, "Handle registration for last saved request");
-                    Message msgReg =
-                            mHandler.obtainMessage(MSG_MNS_NOTIFICATION_REGISTRATION);
+                    if (V) {
+                        Log.v(TAG, "Handle registration for last saved request");
+                    }
+                    Message msgReg = mHandler.obtainMessage(MSG_MNS_NOTIFICATION_REGISTRATION);
                     msgReg.arg1 = mMnsLstRegRqst.lastMasId;
                     msgReg.arg2 = mMnsLstRegRqst.lastNotificationStatus;
-                    if (V) Log.v(TAG, "SearchReg  masId:  " + msgReg.arg1
-                        + " notfStatus: " + msgReg.arg2);
+                    if (V) {
+                        Log.v(TAG, "SearchReg  masId:  " + msgReg.arg1 + " notfStatus: "
+                                + msgReg.arg2);
+                    }
                     //Handle notification registration.
                     mHandler.sendMessageDelayed(msgReg, MNS_NOTIFICATION_DELAY);
                 }
             }
         } else {
-           if (V) Log.v(TAG, "No last saved MNSSDPInfo to handle");
+            if (V) {
+                Log.v(TAG, "No last saved MNSSDPInfo to handle");
+            }
         }
     }
 
@@ -316,8 +347,8 @@
                 // This should not happen...
                 Log.e(TAG, "Invalid SDP content - attempt a connect to UUID...");
                 // TODO: Why insecure? - is it because the link is already encrypted?
-              btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord(
-                      BLUETOOTH_UUID_OBEX_MNS.getUuid());
+                btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord(
+                        BLUETOOTH_UUID_OBEX_MNS.getUuid());
             }
             btSocket.connect();
         } catch (IOException e) {
@@ -339,10 +370,24 @@
             boolean connected = false;
             HeaderSet hs = new HeaderSet();
             // bb582b41-420c-11db-b0de-0800200c9a66
-            byte[] mnsTarget = { (byte) 0xbb, (byte) 0x58, (byte) 0x2b, (byte) 0x41,
-                                 (byte) 0x42, (byte) 0x0c, (byte) 0x11, (byte) 0xdb,
-                                 (byte) 0xb0, (byte) 0xde, (byte) 0x08, (byte) 0x00,
-                                 (byte) 0x20, (byte) 0x0c, (byte) 0x9a, (byte) 0x66 };
+            byte[] mnsTarget = {
+                    (byte) 0xbb,
+                    (byte) 0x58,
+                    (byte) 0x2b,
+                    (byte) 0x41,
+                    (byte) 0x42,
+                    (byte) 0x0c,
+                    (byte) 0x11,
+                    (byte) 0xdb,
+                    (byte) 0xb0,
+                    (byte) 0xde,
+                    (byte) 0x08,
+                    (byte) 0x00,
+                    (byte) 0x20,
+                    (byte) 0x0c,
+                    (byte) 0x9a,
+                    (byte) 0x66
+            };
             hs.setHeader(HeaderSet.TARGET, mnsTarget);
 
             synchronized (this) {
@@ -350,7 +395,9 @@
             }
             try {
                 mHsConnect = mClientSession.connect(hs);
-                if (D) Log.d(TAG, "OBEX session created");
+                if (D) {
+                    Log.d(TAG, "OBEX session created");
+                }
                 connected = true;
             } catch (IOException e) {
                 Log.e(TAG, "OBEX session connect error " + e.getMessage());
@@ -358,7 +405,7 @@
             mConnected = connected;
         }
         synchronized (this) {
-                mWaitingForRemote = false;
+            mWaitingForRemote = false;
         }
     }
 
@@ -369,9 +416,9 @@
      */
     public void sendEvent(byte[] eventBytes, int masInstanceId) {
         // We need to check for null, to handle shutdown.
-        if(mHandler != null) {
+        if (mHandler != null) {
             Message msg = mHandler.obtainMessage(MSG_MNS_SEND_EVENT, masInstanceId, 0, eventBytes);
-            if(msg != null) {
+            if (msg != null) {
                 msg.sendToTarget();
             }
         }
@@ -408,7 +455,7 @@
 
         try {
             request.setHeader(HeaderSet.TYPE, TYPE_EVENT);
-            request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams());
+            request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.encodeParams());
 
             if (mHsConnect.mConnectionID != null) {
                 request.mConnectionID = new byte[4];
@@ -422,8 +469,10 @@
             }
             // Send the header first and then the body
             try {
-                if (V) Log.v(TAG, "Send headerset Event ");
-                putOperation = (ClientOperation)clientSession.put(request);
+                if (V) {
+                    Log.v(TAG, "Send headerset Event ");
+                }
+                putOperation = (ClientOperation) clientSession.put(request);
                 // TODO - Should this be kept or Removed
 
             } catch (IOException e) {
@@ -435,7 +484,9 @@
             }
             if (!error) {
                 try {
-                    if (V) Log.v(TAG, "Send headerset Event ");
+                    if (V) {
+                        Log.v(TAG, "Send headerset Event ");
+                    }
                     outputStream = putOperation.openOutputStream();
                 } catch (IOException e) {
                     Log.e(TAG, "Error when opening OutputStream " + e.getMessage());
@@ -479,7 +530,9 @@
                 if ((!error) && (putOperation != null)) {
                     responseCode = putOperation.getResponseCode();
                     if (responseCode != -1) {
-                        if (V) Log.v(TAG, "Put response code " + responseCode);
+                        if (V) {
+                            Log.v(TAG, "Put response code " + responseCode);
+                        }
                         if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
                             Log.i(TAG, "Response error code is " + responseCode);
                         }
@@ -501,7 +554,7 @@
     }
 
     private void notifyUpdateWakeLock() {
-        if(mCallback != null) {
+        if (mCallback != null) {
             Message msg = Message.obtain(mCallback);
             msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
             msg.sendToTarget();
diff --git a/src/com/android/bluetooth/map/MapContact.java b/src/com/android/bluetooth/map/MapContact.java
index ce2da98..c176be0 100644
--- a/src/com/android/bluetooth/map/MapContact.java
+++ b/src/com/android/bluetooth/map/MapContact.java
@@ -29,7 +29,7 @@
         mName = name;
     }
 
-    public static MapContact create(long id, String name){
+    public static MapContact create(long id, String name) {
         return new MapContact(id, name);
     }
 
@@ -42,21 +42,21 @@
     }
 
     public String getXBtUidString() {
-        if(mId > 0) {
-            return  BluetoothMapUtils.getLongLongAsString(mId, 0);
+        if (mId > 0) {
+            return BluetoothMapUtils.getLongLongAsString(mId, 0);
         }
         return null;
     }
 
     public SignedLongLong getXBtUid() {
-        if(mId > 0) {
-            return  new SignedLongLong(mId, 0);
+        if (mId > 0) {
+            return new SignedLongLong(mId, 0);
         }
         return null;
     }
 
     @Override
-    public String toString(){
+    public String toString() {
         return mName;
     }
 }
diff --git a/src/com/android/bluetooth/map/MmsFileProvider.java b/src/com/android/bluetooth/map/MmsFileProvider.java
index c4faaa8..2b5046a 100644
--- a/src/com/android/bluetooth/map/MmsFileProvider.java
+++ b/src/com/android/bluetooth/map/MmsFileProvider.java
@@ -84,18 +84,18 @@
     @Override
     public ParcelFileDescriptor openFile(Uri uri, String fileMode) throws FileNotFoundException {
         String idStr = uri.getLastPathSegment();
-        if(idStr == null) {
+        if (idStr == null) {
             throw new FileNotFoundException("Unable to extract message handle from: " + uri);
         }
         try {
             long id = Long.parseLong(idStr);
         } catch (NumberFormatException e) {
-            Log.w(TAG,e);
+            Log.w(TAG, e);
             throw new FileNotFoundException("Unable to extract message handle from: " + uri);
         }
         Uri messageUri = Mms.CONTENT_URI.buildUpon().appendEncodedPath(idStr).build();
 
-        return openPipeHelper (messageUri, null, null, null, mPipeWriter);
+        return openPipeHelper(messageUri, null, null, null, mPipeWriter);
     }
 
 
@@ -104,10 +104,13 @@
          * Generate a message based on the cursor, and write the encoded data to the stream.
          */
 
+        @Override
         public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
                 Bundle opts, Cursor c) {
-            if (BluetoothMapService.DEBUG) Log.d(TAG, "writeDataToPipe(): uri=" + uri.toString() +
-                    " - getLastPathSegment() = " + uri.getLastPathSegment());
+            if (BluetoothMapService.DEBUG) {
+                Log.d(TAG, "writeDataToPipe(): uri=" + uri.toString() + " - getLastPathSegment() = "
+                        + uri.getLastPathSegment());
+            }
 
             FileOutputStream fout = null;
             GenericPdu pdu = null;
@@ -131,7 +134,9 @@
                  *       to throw IOException?
                  */
             } finally {
-                if(pduPersister != null) pduPersister.release();
+                if (pduPersister != null) {
+                    pduPersister.release();
+                }
                 try {
                     fout.flush();
                 } catch (IOException e) {
diff --git a/src/com/android/bluetooth/map/SmsMmsContacts.java b/src/com/android/bluetooth/map/SmsMmsContacts.java
index 20ac10e..932e2dd 100644
--- a/src/com/android/bluetooth/map/SmsMmsContacts.java
+++ b/src/com/android/bluetooth/map/SmsMmsContacts.java
@@ -15,10 +15,6 @@
 
 package com.android.bluetooth.map;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.regex.Pattern;
-
 import android.annotation.TargetApi;
 import android.content.ContentResolver;
 import android.database.Cursor;
@@ -30,6 +26,10 @@
 import android.provider.Telephony.MmsSms;
 import android.util.Log;
 
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.regex.Pattern;
+
 /**
  * Use these functions when extracting data for listings. It caches frequently used data to
  * speed up building large listings - e.g. before applying filtering.
@@ -39,14 +39,15 @@
 
     private static final String TAG = "SmsMmsContacts";
 
-    private HashMap<Long,String> mPhoneNumbers = null;
-    private final HashMap<String,MapContact> mNames = new HashMap<String, MapContact>(10);
+    private HashMap<Long, String> mPhoneNumbers = null;
+    private final HashMap<String, MapContact> mNames = new HashMap<String, MapContact>(10);
 
     private static final Uri ADDRESS_URI =
             MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();
 
-    private static final String[] ADDRESS_PROJECTION = { CanonicalAddressesColumns._ID,
-                    CanonicalAddressesColumns.ADDRESS };
+    private static final String[] ADDRESS_PROJECTION = {
+            CanonicalAddressesColumns._ID, CanonicalAddressesColumns.ADDRESS
+    };
     private static final int COL_ADDR_ID =
             Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns._ID);
     private static final int COL_ADDR_ADDR =
@@ -68,7 +69,7 @@
      */
     public String getPhoneNumber(ContentResolver resolver, long id) {
         String number;
-        if(mPhoneNumbers != null && (number = mPhoneNumbers.get(id)) != null) {
+        if (mPhoneNumbers != null && (number = mPhoneNumbers.get(id)) != null) {
             return number;
         }
         fillPhoneCache(resolver);
@@ -80,13 +81,15 @@
         Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, where, null, null);
         try {
             if (c != null) {
-                if(c.moveToPosition(0)) {
+                if (c.moveToPosition(0)) {
                     return c.getString(COL_ADDR_ADDR);
                 }
             }
             Log.e(TAG, "query failed");
         } finally {
-            if(c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
         return null;
     }
@@ -95,8 +98,12 @@
      * Clears the local cache. Call after a listing is complete, to avoid using invalid data.
      */
     public void clearCache() {
-        if(mPhoneNumbers != null) mPhoneNumbers.clear();
-        if(mNames != null) mNames.clear();
+        if (mPhoneNumbers != null) {
+            mPhoneNumbers.clear();
+        }
+        if (mNames != null) {
+            mNames.clear();
+        }
     }
 
     /**
@@ -104,12 +111,11 @@
      * a new query.
      * @param resolver the ContantResolver to be used.
      */
-    private void fillPhoneCache(ContentResolver resolver){
+    private void fillPhoneCache(ContentResolver resolver) {
         Cursor c = resolver.query(ADDRESS_URI, ADDRESS_PROJECTION, null, null, null);
-        if(mPhoneNumbers == null) {
+        if (mPhoneNumbers == null) {
             int size = 0;
-            if(c != null)
-            {
+            if (c != null) {
                 size = c.getCount();
             }
             mPhoneNumbers = new HashMap<Long, String>(size);
@@ -130,13 +136,16 @@
                 Log.e(TAG, "query failed");
             }
         } finally {
-            if(c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
     }
 
     public MapContact getContactNameFromPhone(String phone, ContentResolver resolver) {
         return getContactNameFromPhone(phone, resolver, null);
     }
+
     /**
      * Lookup a contacts name in the Android Contacts database.
      * @param phone the phone number of the contact
@@ -147,18 +156,18 @@
             String contactNameFilter) {
         MapContact contact = mNames.get(phone);
 
-        if(contact != null){
-            if(contact.getId() < 0) {
+        if (contact != null) {
+            if (contact.getId() < 0) {
                 return null;
             }
-            if(contactNameFilter == null) {
+            if (contactNameFilter == null) {
                 return contact;
             }
             // Validate filter
             String searchString = contactNameFilter.replace("*", ".*");
             searchString = ".*" + searchString + ".*";
             Pattern p = Pattern.compile(Pattern.quote(searchString), Pattern.CASE_INSENSITIVE);
-            if(p.matcher(contact.getName()).find()) {
+            if (p.matcher(contact.getName()).find()) {
                 return contact;
             }
             return null;
@@ -166,11 +175,11 @@
 
         // TODO: Should we change to extract both formatted name, and display name?
 
-        Uri uri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
-                Uri.encode(phone));
+        Uri uri =
+                Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phone));
         String selection = CONTACT_SEL_VISIBLE;
         String[] selectionArgs = null;
-        if(contactNameFilter != null) {
+        if (contactNameFilter != null) {
             selection += "AND " + ContactsContract.Contacts.DISPLAY_NAME + " like ?";
             selectionArgs = new String[]{"%" + contactNameFilter.replace("*", "%") + "%"};
         }
@@ -189,7 +198,9 @@
                 contact = null;
             }
         } finally {
-            if (c != null) c.close();
+            if (c != null) {
+                c.close();
+            }
         }
         return contact;
     }
diff --git a/src/com/android/bluetooth/mapclient/MapClientService.java b/src/com/android/bluetooth/mapclient/MapClientService.java
index ce6aee2..0d77c8e 100644
--- a/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -21,9 +21,17 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothMapClient;
+import android.bluetooth.SdpMasRecord;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+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;
@@ -31,8 +39,11 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 public class MapClientService extends ProfileService {
     private static final String TAG = "MapClientService";
@@ -40,60 +51,143 @@
     static final boolean DBG = false;
     static final boolean VDBG = false;
 
-    private static final int MAXIMUM_CONNECTED_DEVICES = 1;
+    static final int MAXIMUM_CONNECTED_DEVICES = 4;
 
     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
 
-    MceStateMachine mMceStateMachine;
+    private Map<BluetoothDevice, MceStateMachine> mMapInstanceMap = new ConcurrentHashMap<>(1);
     private MnsService mMnsServer;
     private BluetoothAdapter mAdapter;
     private static MapClientService sMapClientService;
-
+    private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();
 
     public static synchronized MapClientService getMapClientService() {
-        if (sMapClientService != null && sMapClientService.isAvailable()) {
-            if (DBG) Log.d(TAG, "getMapClientService(): returning " + sMapClientService);
-            return sMapClientService;
+        if (sMapClientService == null) {
+            Log.w(TAG, "getMapClientService(): service is null");
+            return null;
         }
-        if (DBG) {
-            if (sMapClientService == null) {
-                Log.d(TAG, "getMapClientService(): service is NULL");
-            } else if (!(sMapClientService.isAvailable())) {
-                Log.d(TAG, "getMapClientService(): service is not available");
-            }
+        if (!sMapClientService.isAvailable()) {
+            Log.w(TAG, "getMapClientService(): service is not available ");
+            return null;
         }
-        return null;
+        return sMapClientService;
     }
 
-    private static synchronized void setService(MapClientService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) Log.d(TAG, "setMapMceService(): replacing old instance: " + sMapClientService);
-            sMapClientService = instance;
-        } else {
-            if (DBG) {
-                if (sMapClientService == null) {
-                    Log.d(TAG, "setA2dpService(): service not available");
-                } else if (!sMapClientService.isAvailable()) {
-                    Log.d(TAG, "setA2dpService(): service is cleaning up");
+    private static synchronized void setMapClientService(MapClientService instance) {
+        if (DBG) {
+            Log.d(TAG, "setMapClientService(): set to: " + instance);
+        }
+        sMapClientService = instance;
+    }
+
+    @VisibleForTesting
+    Map<BluetoothDevice, MceStateMachine> getInstanceMap() {
+        return mMapInstanceMap;
+    }
+
+    /**
+     * Connect the given Bluetooth device.
+     *
+     * @param 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, "MAP connect device: " + device
+                    + ", InstanceMap start state: " + sb.toString());
+        }
+        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
+        if (mapStateMachine == null) {
+            // a map state machine instance doesn't exist yet, create a new one if we can.
+            if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) {
+                addDeviceToMapAndConnect(device);
+                return true;
+            } else {
+                // Maxed out on the number of allowed connections.
+                // see if some of the current connections can be cleaned-up, to make room.
+                removeUncleanAccounts();
+                if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) {
+                    addDeviceToMapAndConnect(device);
+                    return true;
+                } else {
+                    Log.e(TAG, "Maxed out on the number of allowed MAP connections. "
+                            + "Connect request rejected on " + device);
+                    return false;
                 }
             }
         }
+
+        // statemachine already exists in the map.
+        int state = getConnectionState(device);
+        if (state == BluetoothProfile.STATE_CONNECTED
+                || state == BluetoothProfile.STATE_CONNECTING) {
+            Log.w(TAG, "Received connect request while already connecting/connected.");
+            return true;
+        }
+
+        // Statemachine exists but not in connecting or connected state! it should
+        // have been removed form the map. lets get rid of it and add a new one.
+        if (DBG) {
+            Log.d(TAG, "Statemachine exists for a device in unexpected state: " + state);
+        }
+        mMapInstanceMap.remove(device);
+        addDeviceToMapAndConnect(device);
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "MAP connect device: " + device
+                    + ", InstanceMap end state: " + sb.toString());
+        }
+        return true;
     }
 
-    public synchronized boolean connect(BluetoothDevice device) {
-        Log.d(TAG, "MAP Mce connect " + device.toString());
-        return mMceStateMachine.connect(device);
+    private synchronized void addDeviceToMapAndConnect(BluetoothDevice device) {
+        // When creating a new statemachine, its state is set to CONNECTING - which will trigger
+        // connect.
+        MceStateMachine mapStateMachine = new MceStateMachine(this, device);
+        mMapInstanceMap.put(device, mapStateMachine);
     }
 
     public synchronized boolean disconnect(BluetoothDevice device) {
-        Log.d(TAG, "MAP Mce disconnect " + device.toString());
-        return mMceStateMachine.disconnect(device);
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "MAP disconnect device: " + device
+                    + ", InstanceMap start state: " + sb.toString());
+        }
+        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
+        // a map state machine instance doesn't exist. maybe it is already gone?
+        if (mapStateMachine == null) {
+            return false;
+        }
+        int connectionState = mapStateMachine.getState();
+        if (connectionState != BluetoothProfile.STATE_CONNECTED
+                && connectionState != BluetoothProfile.STATE_CONNECTING) {
+            return false;
+        }
+        mapStateMachine.disconnect();
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "MAP disconnect device: " + device
+                    + ", InstanceMap start state: " + sb.toString());
+        }
+        return true;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
         return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
     }
 
+    MceStateMachine getMceStateMachineForDevice(BluetoothDevice device) {
+        return mMapInstanceMap.get(device);
+    }
+
     public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
         List<BluetoothDevice> deviceList = new ArrayList<>();
@@ -113,18 +207,18 @@
     }
 
     public synchronized int getConnectionState(BluetoothDevice device) {
-        if (mMceStateMachine != null && device.equals(mMceStateMachine.getDevice())) {
-            return mMceStateMachine.getState();
-        } else {
-            return BluetoothProfile.STATE_DISCONNECTED;
-        }
+        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
+        // a map state machine instance doesn't exist yet, create a new one if we can.
+        return (mapStateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
+                : mapStateMachine.getState();
     }
 
     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);
+                Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()), priority);
+        if (VDBG) {
+            Log.v(TAG, "Saved priority " + device + " = " + priority);
+        }
         return true;
     }
 
@@ -137,12 +231,9 @@
 
     public synchronized boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
             PendingIntent sentIntent, PendingIntent deliveredIntent) {
-        if (mMceStateMachine != null && device.equals(mMceStateMachine.getDevice())) {
-            return mMceStateMachine.sendMapMessage(contacts, message, sentIntent, deliveredIntent);
-        } else {
-            return false;
-        }
-
+        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
+        return mapStateMachine != null
+                && mapStateMachine.sendMapMessage(contacts, message, sentIntent, deliveredIntent);
     }
 
     @Override
@@ -152,64 +243,131 @@
 
     @Override
     protected boolean start() {
-        if (DBG) Log.d(TAG, "start()");
-        setService(this);
+        Log.e(TAG, "start()");
 
         if (mMnsServer == null) {
-            mMnsServer = new MnsService(this);
-        }
-        if (mMceStateMachine == null) {
-            mMceStateMachine = new MceStateMachine(this);
+            mMnsServer = MapUtils.newMnsServiceInstance(this);
+            if (mMnsServer == null) {
+                // this can't happen
+                Log.w(TAG, "MnsService is *not* created!");
+                return false;
+            }
         }
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        mStartError = false;
-        return !mStartError;
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
+        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+        registerReceiver(mMapReceiver, filter);
+        removeUncleanAccounts();
+        setMapClientService(this);
+        return true;
     }
 
     @Override
     protected synchronized boolean stop() {
-        if (DBG) Log.d(TAG, "stop()");
+        if (DBG) {
+            Log.d(TAG, "stop()");
+        }
+        unregisterReceiver(mMapReceiver);
         if (mMnsServer != null) {
             mMnsServer.stop();
         }
-        if (mMceStateMachine.getState() == BluetoothAdapter.STATE_CONNECTED) {
-            mMceStateMachine.disconnect(mMceStateMachine.getDevice());
+        for (MceStateMachine stateMachine : mMapInstanceMap.values()) {
+            if (stateMachine.getState() == BluetoothAdapter.STATE_CONNECTED) {
+                stateMachine.disconnect();
+            }
+            stateMachine.doQuit();
         }
-        mMceStateMachine.doQuit();
         return true;
     }
 
-    public boolean cleanup() {
-        if (DBG) Log.d(TAG, "cleanup()");
-        return true;
-    }
-
-    public synchronized boolean getUnreadMessages(BluetoothDevice device) {
-        if (mMceStateMachine != null && device.equals(mMceStateMachine.getDevice())) {
-            return mMceStateMachine.getUnreadMessages();
-        } else {
-            return false;
-        }
-    }
-
     @Override
-    public synchronized void dump(StringBuilder sb) {
+    protected void cleanup() {
+        if (DBG) {
+            Log.d(TAG, "in Cleanup");
+        }
+        removeUncleanAccounts();
+        // TODO(b/72948646): should be moved to stop()
+        setMapClientService(null);
+    }
+
+    void cleanupDevice(BluetoothDevice device) {
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "Cleanup device: " + device + ", InstanceMap start state: "
+                    + sb.toString());
+        }
+        synchronized (mMapInstanceMap) {
+            MceStateMachine stateMachine = mMapInstanceMap.get(device);
+            if (stateMachine != null) {
+                mMapInstanceMap.remove(device);
+            }
+        }
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "Cleanup device: " + device + ", InstanceMap end state: "
+                    + sb.toString());
+        }
+    }
+
+    @VisibleForTesting
+    void removeUncleanAccounts() {
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: "
+                    + sb.toString());
+        }
+        Iterator iterator = mMapInstanceMap.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<BluetoothDevice, MceStateMachine> profileConnection =
+                    (Map.Entry) iterator.next();
+            if (profileConnection.getValue().getState() == BluetoothProfile.STATE_DISCONNECTED) {
+                iterator.remove();
+            }
+        }
+        if (DBG) {
+            StringBuilder sb = new StringBuilder();
+            dump(sb);
+            Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: "
+                    + sb.toString());
+        }
+    }
+
+    public synchronized boolean getUnreadMessages(BluetoothDevice device) {
+        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
+        if (mapStateMachine == null) {
+            return false;
+        }
+        return mapStateMachine.getUnreadMessages();
+    }
+
+    @Override
+    public void dump(StringBuilder sb) {
         super.dump(sb);
-        println(sb, "StateMachine: " + mMceStateMachine.toString());
+        ProfileService.println(sb, "# Services Connected: " + mMapInstanceMap.size());
+        for (MceStateMachine stateMachine : mMapInstanceMap.values()) {
+            stateMachine.dump(sb);
+        }
     }
 
     //Binder object: Must be static class or memory leak may occur
+
     /**
      * This class implements the IClient interface - or actually it validates the
      * preconditions for calling the actual functionality in the MapClientService, and calls it.
      */
-    private static class Binder extends IBluetoothMapClient.Stub
-            implements IProfileServiceBinder {
+    private static class Binder extends IBluetoothMapClient.Stub implements IProfileServiceBinder {
         private MapClientService mService;
 
         Binder(MapClientService service) {
-            if (VDBG) Log.v(TAG, "Binder()");
+            if (VDBG) {
+                Log.v(TAG, "Binder()");
+            }
             mService = service;
         }
 
@@ -227,69 +385,108 @@
             return null;
         }
 
-        public boolean cleanup() {
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
+        @Override
         public boolean isConnected(BluetoothDevice device) {
-            if (VDBG) Log.v(TAG, "isConnected()");
+            if (VDBG) {
+                Log.v(TAG, "isConnected()");
+            }
             MapClientService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED;
         }
 
+        @Override
         public boolean connect(BluetoothDevice device) {
-            if (VDBG) Log.v(TAG, "connect()");
+            if (VDBG) {
+                Log.v(TAG, "connect()");
+            }
             MapClientService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.connect(device);
         }
 
+        @Override
         public boolean disconnect(BluetoothDevice device) {
-            if (VDBG) Log.v(TAG, "disconnect()");
+            if (VDBG) {
+                Log.v(TAG, "disconnect()");
+            }
             MapClientService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.disconnect(device);
         }
 
+        @Override
         public List<BluetoothDevice> getConnectedDevices() {
-            if (VDBG) Log.v(TAG, "getConnectedDevices()");
+            if (VDBG) {
+                Log.v(TAG, "getConnectedDevices()");
+            }
             MapClientService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getConnectedDevices();
         }
 
+        @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            if (VDBG) Log.v(TAG, "getDevicesMatchingConnectionStates()");
+            if (VDBG) {
+                Log.v(TAG, "getDevicesMatchingConnectionStates()");
+            }
             MapClientService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getDevicesMatchingConnectionStates(states);
         }
 
+        @Override
         public int getConnectionState(BluetoothDevice device) {
-            if (VDBG) Log.v(TAG, "getConnectionState()");
+            if (VDBG) {
+                Log.v(TAG, "getConnectionState()");
+            }
             MapClientService service = getService();
-            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
             return service.getConnectionState(device);
         }
 
+        @Override
         public boolean setPriority(BluetoothDevice device, int priority) {
             MapClientService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPriority(device, priority);
         }
 
+        @Override
         public int getPriority(BluetoothDevice device) {
             MapClientService service = getService();
-            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
+            if (service == null) {
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
             return service.getPriority(device);
         }
 
+        @Override
         public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
                 PendingIntent sentIntent, PendingIntent deliveredIntent) {
             MapClientService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             Log.d(TAG, "Checking Permission of sendMessage");
             mService.enforceCallingOrSelfPermission(Manifest.permission.SEND_SMS,
                     "Need SEND_SMS permission");
@@ -297,12 +494,73 @@
             return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
         }
 
+        @Override
         public boolean getUnreadMessages(BluetoothDevice device) {
             MapClientService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             mService.enforceCallingOrSelfPermission(Manifest.permission.READ_SMS,
                     "Need READ_SMS permission");
             return service.getUnreadMessages(device);
         }
     }
+
+    private class MapBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (DBG) {
+                Log.d(TAG, "onReceive: " + action);
+            }
+            if (!action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)
+                    && !action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
+                // we don't care about this intent
+                return;
+            }
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            if (device == null) {
+                Log.e(TAG, "broadcast has NO device param!");
+                return;
+            }
+            if (DBG) {
+                Log.d(TAG, "broadcast has device: (" + device.getAddress() + ", "
+                        + device.getName() + ")");
+            }
+            MceStateMachine stateMachine = mMapInstanceMap.get(device);
+            if (stateMachine == null) {
+                Log.e(TAG, "No Statemachine found for the device from broadcast");
+                return;
+            }
+
+            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
+                if (stateMachine.getState() == BluetoothProfile.STATE_CONNECTED) {
+                    stateMachine.disconnect();
+                }
+            }
+
+            if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
+                ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
+                if (DBG) {
+                    Log.d(TAG, "UUID of SDP: " + uuid);
+                }
+
+                if (uuid.equals(BluetoothUuid.MAS)) {
+                    // Check if we have a valid SDP record.
+                    SdpMasRecord masRecord =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
+                    if (DBG) {
+                        Log.d(TAG, "SDP = " + masRecord);
+                    }
+                    int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
+                    if (masRecord == null) {
+                        Log.w(TAG, "SDP search ended with no MAS record. Status: " + status);
+                        return;
+                    }
+                    stateMachine.obtainMessage(MceStateMachine.MSG_MAS_SDP_DONE,
+                            masRecord).sendToTarget();
+                }
+            }
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/mapclient/MapUtils.java b/src/com/android/bluetooth/mapclient/MapUtils.java
new file mode 100644
index 0000000..4ed26c8
--- /dev/null
+++ b/src/com/android/bluetooth/mapclient/MapUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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 android.support.annotation.VisibleForTesting;
+
+class MapUtils {
+    private static MnsService sMnsService = null;
+
+    @VisibleForTesting
+    static void setMnsService(MnsService service) {
+        sMnsService = service;
+    }
+
+    static MnsService newMnsServiceInstance(MapClientService mapClientService) {
+        return (sMnsService == null) ? new MnsService(mapClientService) : sMnsService;
+    }
+}
diff --git a/src/com/android/bluetooth/mapclient/MasClient.java b/src/com/android/bluetooth/mapclient/MasClient.java
index 922ec4a..d7283bf 100644
--- a/src/com/android/bluetooth/mapclient/MasClient.java
+++ b/src/com/android/bluetooth/mapclient/MasClient.java
@@ -34,6 +34,7 @@
 import javax.obex.ClientSession;
 import javax.obex.HeaderSet;
 import javax.obex.ResponseCodes;
+
 /* MasClient is a one time use connection to a server defined by the SDP record passed in at
  * construction.  After use shutdown() must be called to properly clean up.
  */
@@ -45,12 +46,28 @@
     private static final boolean DBG = MapClientService.DBG;
     private static final boolean VDBG = MapClientService.VDBG;
     private static final byte[] BLUETOOTH_UUID_OBEX_MAS = new byte[]{
-            (byte) 0xbb, 0x58, 0x2b, 0x40, 0x42, 0x0c, 0x11, (byte) 0xdb, (byte) 0xb0, (byte) 0xde,
-            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
+            (byte) 0xbb,
+            0x58,
+            0x2b,
+            0x40,
+            0x42,
+            0x0c,
+            0x11,
+            (byte) 0xdb,
+            (byte) 0xb0,
+            (byte) 0xde,
+            0x08,
+            0x00,
+            0x20,
+            0x0c,
+            (byte) 0x9a,
+            0x66
     };
     private static final byte OAP_TAGID_MAP_SUPPORTED_FEATURES = 0x29;
     private static final int MAP_FEATURE_NOTIFICATION_REGISTRATION = 0x00000001;
-    private static final int MAP_SUPPORTED_FEATURES = MAP_FEATURE_NOTIFICATION_REGISTRATION;
+    private static final int MAP_FEATURE_NOTIFICATION = 0x00000002;
+    static final int MAP_SUPPORTED_FEATURES =
+            MAP_FEATURE_NOTIFICATION_REGISTRATION | MAP_FEATURE_NOTIFICATION;
 
     private final StateMachine mCallback;
     private Handler mHandler;
@@ -58,23 +75,23 @@
     private BluetoothObexTransport mTransport;
     private BluetoothDevice mRemoteDevice;
     private ClientSession mSession;
-    private HandlerThread thread;
+    private HandlerThread mThread;
     private boolean mConnected = false;
     SdpMasRecord mSdpMasRecord;
 
-    public MasClient(BluetoothDevice remoteDevice,
-            StateMachine callback, SdpMasRecord sdpMasRecord) {
+    public MasClient(BluetoothDevice remoteDevice, StateMachine callback,
+            SdpMasRecord sdpMasRecord) {
         if (remoteDevice == null) {
             throw new NullPointerException("Obex transport is null");
         }
         mRemoteDevice = remoteDevice;
         mCallback = callback;
         mSdpMasRecord = sdpMasRecord;
-        thread = new HandlerThread("Client");
-        thread.start();
+        mThread = new HandlerThread("Client");
+        mThread.start();
         /* This will block until the looper have started, hence it will be safe to use it,
            when the constructor completes */
-        Looper looper = thread.getLooper();
+        Looper looper = mThread.getLooper();
         mHandler = new MasClientHandler(looper, this);
 
         mHandler.obtainMessage(CONNECT).sendToTarget();
@@ -96,8 +113,7 @@
             headerset.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_OBEX_MAS);
             ObexAppParameters oap = new ObexAppParameters();
 
-            oap.add(OAP_TAGID_MAP_SUPPORTED_FEATURES,
-                    MAP_SUPPORTED_FEATURES);
+            oap.add(OAP_TAGID_MAP_SUPPORTED_FEATURES, MAP_SUPPORTED_FEATURES);
 
             oap.addToHeaderSet(headerset);
 
@@ -105,10 +121,11 @@
             Log.d(TAG, "Connection results" + headerset.getResponseCode());
 
             if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) {
-                if (DBG) Log.d(TAG, "Connection Successful");
+                if (DBG) {
+                    Log.d(TAG, "Connection Successful");
+                }
                 mConnected = true;
-                mCallback.obtainMessage(
-                        MceStateMachine.MSG_MAS_CONNECTED).sendToTarget();
+                mCallback.sendMessage(MceStateMachine.MSG_MAS_CONNECTED);
             } else {
                 disconnect();
             }
@@ -135,23 +152,26 @@
         }
 
         mConnected = false;
-        mCallback.obtainMessage(MceStateMachine.MSG_MAS_DISCONNECTED).sendToTarget();
+        mCallback.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED);
     }
 
     private void executeRequest(Request request) {
         try {
             request.execute(mSession);
-            mCallback.obtainMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
-                    request).sendToTarget();
+            mCallback.sendMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, request);
         } catch (IOException e) {
-            if (DBG) Log.d(TAG, "Request failed: " + request);
+            if (DBG) {
+                Log.d(TAG, "Request failed: " + request);
+            }
             // Disconnect to cleanup.
             disconnect();
         }
     }
 
     public boolean makeRequest(Request request) {
-        if (DBG) Log.d(TAG, "makeRequest called with: " + request);
+        if (DBG) {
+            Log.d(TAG, "makeRequest called with: " + request);
+        }
 
         boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request));
         if (!status) {
@@ -163,7 +183,7 @@
 
     public void shutdown() {
         mHandler.obtainMessage(DISCONNECT).sendToTarget();
-        thread.quitSafely();
+        mThread.quitSafely();
     }
 
     public enum CharsetType {
diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java
index 810979c..ae11d2a 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -40,6 +40,7 @@
  */
 package com.android.bluetooth.mapclient;
 
+import android.app.Activity;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -47,18 +48,17 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.SdpMasRecord;
-import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Message;
-import android.os.ParcelUuid;
-import android.provider.ContactsContract;
 import android.telecom.PhoneAccount;
+import android.telephony.SmsManager;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothMetricsProto;
+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;
@@ -67,6 +67,7 @@
 import com.android.vcard.VCardProperty;
 
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.HashMap;
 import java.util.List;
 
@@ -112,24 +113,28 @@
     private State mConnected;
     private State mDisconnecting;
 
-    private BluetoothDevice mDevice;
+    private final BluetoothDevice mDevice;
     private MapClientService mService;
     private MasClient mMasClient;
-    private HashMap<String, Bmessage> sentMessageLog =
+    private HashMap<String, Bmessage> mSentMessageLog = new HashMap<>(MAX_MESSAGES);
+    private HashMap<Bmessage, PendingIntent> mSentReceiptRequested = new HashMap<>(MAX_MESSAGES);
+    private HashMap<Bmessage, PendingIntent> mDeliveryReceiptRequested =
             new HashMap<>(MAX_MESSAGES);
-    private HashMap<Bmessage, PendingIntent> sentReceiptRequested = new HashMap<>(
-            MAX_MESSAGES);
-    private HashMap<Bmessage, PendingIntent> deliveryReceiptRequested = new HashMap<>(
-            MAX_MESSAGES);
     private Bmessage.Type mDefaultMessageType = Bmessage.Type.SMS_CDMA;
-    private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();
 
-    MceStateMachine(MapClientService service) {
+    MceStateMachine(MapClientService service, BluetoothDevice device) {
+        this(service, device, null);
+    }
+
+    @VisibleForTesting
+    MceStateMachine(MapClientService service, BluetoothDevice device, MasClient masClient) {
         super(TAG);
+        mMasClient = masClient;
         mService = service;
 
         mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
 
+        mDevice = device;
         mDisconnected = new Disconnected();
         mConnecting = new Connecting();
         mDisconnecting = new Disconnecting();
@@ -139,7 +144,7 @@
         addState(mConnecting);
         addState(mDisconnecting);
         addState(mConnected);
-        setInitialState(mDisconnected);
+        setInitialState(mConnecting);
         start();
     }
 
@@ -147,14 +152,28 @@
         quitNow();
     }
 
+    @Override
+    protected void onQuitting() {
+        if (mService != null) {
+            mService.cleanupDevice(mDevice);
+        }
+    }
+
     synchronized BluetoothDevice getDevice() {
         return mDevice;
     }
 
     private void onConnectionStateChanged(int prevState, int state) {
         // mDevice == null only at setInitialState
-        if (mDevice == null) return;
-        if (DBG) Log.d(TAG, "Connection state " + mDevice + ": " + prevState + "->" + state);
+        if (mDevice == null) {
+            return;
+        }
+        if (DBG) {
+            Log.d(TAG, "Connection state " + mDevice + ": " + prevState + "->" + state);
+        }
+        if (prevState != state && state == BluetoothProfile.STATE_CONNECTED) {
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.MAP_CLIENT);
+        }
         Intent intent = new Intent(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
         intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
@@ -180,23 +199,22 @@
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (DBG) Log.d(TAG, "Connect Request " + device.getAddress());
-        sendMessage(MSG_CONNECT, device);
-        return true;
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (DBG) Log.d(TAG, "Disconnect Request " + device.getAddress());
-        sendMessage(MSG_DISCONNECT, device);
+    public boolean disconnect() {
+        if (DBG) {
+            Log.d(TAG, "Disconnect Request " + mDevice.getAddress());
+        }
+        sendMessage(MSG_DISCONNECT, mDevice);
         return true;
     }
 
     public synchronized boolean sendMapMessage(Uri[] contacts, String message,
-            PendingIntent sentIntent,
-            PendingIntent deliveredIntent) {
-        if (DBG) Log.d(TAG, "Send Message " + message);
-        if (contacts == null || contacts.length <= 0) return false;
+            PendingIntent sentIntent, PendingIntent deliveredIntent) {
+        if (DBG) {
+            Log.d(TAG, "Send Message " + message);
+        }
+        if (contacts == null || contacts.length <= 0) {
+            return false;
+        }
         if (this.getCurrentState() == mConnected) {
             Bmessage bmsg = new Bmessage();
             // Set type and status.
@@ -205,31 +223,34 @@
 
             for (Uri contact : contacts) {
                 // Who to send the message to.
-                VCardEntry dest_entry = new VCardEntry();
-                VCardProperty dest_entry_phone = new VCardProperty();
-                if (DBG) Log.d(TAG, "Scheme " + contact.getScheme());
+                VCardEntry destEntry = new VCardEntry();
+                VCardProperty destEntryPhone = new VCardProperty();
+                if (DBG) {
+                    Log.d(TAG, "Scheme " + contact.getScheme());
+                }
                 if (PhoneAccount.SCHEME_TEL.equals(contact.getScheme())) {
-                    dest_entry_phone.setName(VCardConstants.PROPERTY_TEL);
-                    dest_entry_phone.addValues(contact.getSchemeSpecificPart());
+                    destEntryPhone.setName(VCardConstants.PROPERTY_TEL);
+                    destEntryPhone.addValues(contact.getSchemeSpecificPart());
                     if (DBG) {
-                        Log.d(TAG,
-                                "Sending to phone numbers " + dest_entry_phone.getValueList());
+                        Log.d(TAG, "Sending to phone numbers " + destEntryPhone.getValueList());
                     }
                 } else {
-                    if (DBG) Log.w(TAG, "Scheme " + contact.getScheme() + " not supported.");
+                    if (DBG) {
+                        Log.w(TAG, "Scheme " + contact.getScheme() + " not supported.");
+                    }
                     return false;
                 }
-                dest_entry.addProperty(dest_entry_phone);
-                bmsg.addRecipient(dest_entry);
+                destEntry.addProperty(destEntryPhone);
+                bmsg.addRecipient(destEntry);
             }
 
             // Message of the body.
             bmsg.setBodyContent(message);
             if (sentIntent != null) {
-                sentReceiptRequested.put(bmsg, sentIntent);
+                mSentReceiptRequested.put(bmsg, sentIntent);
             }
             if (deliveredIntent != null) {
-                deliveryReceiptRequested.put(bmsg, deliveredIntent);
+                mDeliveryReceiptRequested.put(bmsg, deliveredIntent);
             }
             sendMessage(MSG_OUTBOUND_MESSAGE, bmsg);
             return true;
@@ -238,7 +259,9 @@
     }
 
     synchronized boolean getMessage(String handle) {
-        if (DBG) Log.d(TAG, "getMessage" + handle);
+        if (DBG) {
+            Log.d(TAG, "getMessage" + handle);
+        }
         if (this.getCurrentState() == mConnected) {
             sendMessage(MSG_INBOUND_MESSAGE, handle);
             return true;
@@ -247,7 +270,9 @@
     }
 
     synchronized boolean getUnreadMessages() {
-        if (DBG) Log.d(TAG, "getMessage");
+        if (DBG) {
+            Log.d(TAG, "getMessage");
+        }
         if (this.getCurrentState() == mConnected) {
             sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_INBOX);
             return true;
@@ -276,31 +301,20 @@
         }
     }
 
+    public void dump(StringBuilder sb) {
+        ProfileService.println(sb, "mCurrentDevice: " + mDevice.getAddress() + " (name = "
+                + mDevice.getName() + "), StateMachine: " + this.toString());
+    }
+
     class Disconnected extends State {
         @Override
         public void enter() {
-            if (DBG) Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
-            onConnectionStateChanged(mPreviousState,
-                    BluetoothProfile.STATE_DISCONNECTED);
-            mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case MSG_CONNECT:
-                    synchronized (MceStateMachine.this) {
-                        mDevice = (BluetoothDevice) message.obj;
-                    }
-                    transitionTo(mConnecting);
-                    break;
-
-                default:
-                    Log.w(TAG, "Unexpected message: " + message.what + " from state:" +
-                        this.getName());
-                    return NOT_HANDLED;
+            if (DBG) {
+                Log.d(TAG, "Enter Disconnected: " + getCurrentMessage().what);
             }
-            return HANDLED;
+            onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTED);
+            mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
+            quit();
         }
 
         @Override
@@ -312,15 +326,10 @@
     class Connecting extends State {
         @Override
         public void enter() {
-            if (DBG) Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
-            onConnectionStateChanged(mPreviousState,
-                    BluetoothProfile.STATE_CONNECTING);
-
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
-            filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
-            // unregisterReceiver in Disconnecting
-            mService.registerReceiver(mMapReceiver, filter);
+            if (DBG) {
+                Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
+            }
+            onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTING);
 
             BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
             // When commanded to connect begin SDP to find the MAS server.
@@ -330,14 +339,17 @@
 
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) Log.d(TAG, "processMessage" + this.getName() + message.what);
+            if (DBG) {
+                Log.d(TAG, "processMessage" + this.getName() + message.what);
+            }
 
             switch (message.what) {
                 case MSG_MAS_SDP_DONE:
-                    if (DBG) Log.d(TAG, "SDP Complete");
+                    if (DBG) {
+                        Log.d(TAG, "SDP Complete");
+                    }
                     if (mMasClient == null) {
-                        mMasClient = new MasClient(mDevice,
-                                MceStateMachine.this,
+                        mMasClient = new MasClient(mDevice, MceStateMachine.this,
                                 (SdpMasRecord) message.obj);
                         setDefaultMessageType((SdpMasRecord) message.obj);
                     }
@@ -347,6 +359,10 @@
                     transitionTo(mConnected);
                     break;
 
+                case MSG_MAS_DISCONNECTED:
+                    transitionTo(mDisconnected);
+                    break;
+
                 case MSG_CONNECTING_TIMEOUT:
                     transitionTo(mDisconnecting);
                     break;
@@ -357,8 +373,8 @@
                     break;
 
                 default:
-                    Log.w(TAG, "Unexpected message: " + message.what + " from state:" +
-                        this.getName());
+                    Log.w(TAG, "Unexpected message: " + message.what + " from state:"
+                            + this.getName());
                     return NOT_HANDLED;
             }
             return HANDLED;
@@ -374,9 +390,10 @@
     class Connected extends State {
         @Override
         public void enter() {
-            if (DBG) Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
-            onConnectionStateChanged(mPreviousState,
-                    BluetoothProfile.STATE_CONNECTED);
+            if (DBG) {
+                Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
+            }
+            onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTED);
 
             mMasClient.makeRequest(new RequestSetPath(FOLDER_TELECOM));
             mMasClient.makeRequest(new RequestSetPath(FOLDER_MSG));
@@ -396,13 +413,15 @@
                     break;
 
                 case MSG_OUTBOUND_MESSAGE:
-                    mMasClient.makeRequest(new RequestPushMessage(FOLDER_OUTBOX,
-                            (Bmessage) message.obj, null, false, false));
+                    mMasClient.makeRequest(
+                            new RequestPushMessage(FOLDER_OUTBOX, (Bmessage) message.obj, null,
+                                    false, false));
                     break;
 
                 case MSG_INBOUND_MESSAGE:
-                    mMasClient.makeRequest(new RequestGetMessage((String) message.obj,
-                            MasClient.CharsetType.UTF_8, false));
+                    mMasClient.makeRequest(
+                            new RequestGetMessage((String) message.obj, MasClient.CharsetType.UTF_8,
+                                    false));
                     break;
 
                 case MSG_NOTIFICATION:
@@ -414,22 +433,30 @@
                     break;
 
                 case MSG_GET_MESSAGE_LISTING:
+                    // Get latest 50 Unread messages in the last week
                     MessagesFilter filter = new MessagesFilter();
                     filter.setMessageType((byte) 0);
-                    mMasClient.makeRequest(
-                            new RequestGetMessagesListing((String) message.obj, 0,
-                                    filter, 0, 1, 0));
+                    filter.setReadStatus(MessagesFilter.READ_STATUS_UNREAD);
+                    Calendar calendar = Calendar.getInstance();
+                    calendar.add(Calendar.DATE, -7);
+                    filter.setPeriod(calendar.getTime(), null);
+                    mMasClient.makeRequest(new RequestGetMessagesListing(
+                            (String) message.obj, 0, filter, 0, 50, 0));
                     break;
 
                 case MSG_MAS_REQUEST_COMPLETED:
-                    if (DBG) Log.d(TAG, "Completed request");
+                    if (DBG) {
+                        Log.d(TAG, "Completed request");
+                    }
                     if (message.obj instanceof RequestGetMessage) {
                         processInboundMessage((RequestGetMessage) message.obj);
                     } else if (message.obj instanceof RequestPushMessage) {
-                        String messageHandle =
-                                ((RequestPushMessage) message.obj).getMsgHandle();
-                        if (DBG) Log.d(TAG, "Message Sent......." + messageHandle);
-                        sentMessageLog.put(messageHandle,
+                        String messageHandle = ((RequestPushMessage) message.obj).getMsgHandle();
+                        if (DBG) {
+                            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());
                     } else if (message.obj instanceof RequestGetMessagesListing) {
                         processMessageListing((RequestGetMessagesListing) message.obj);
@@ -444,8 +471,8 @@
                     break;
 
                 default:
-                    Log.w(TAG, "Unexpected message: " + message.what + " from state:" +
-                        this.getName());
+                    Log.w(TAG, "Unexpected message: " + message.what + " from state:"
+                            + this.getName());
                     return NOT_HANDLED;
             }
             return HANDLED;
@@ -457,13 +484,19 @@
         }
 
         private void processNotification(Message msg) {
-            if (DBG) Log.d(TAG, "Handler: msg: " + msg.what);
+            if (DBG) {
+                Log.d(TAG, "Handler: msg: " + msg.what);
+            }
 
             switch (msg.what) {
                 case MSG_NOTIFICATION:
                     EventReport ev = (EventReport) msg.obj;
-                    if (DBG) Log.d(TAG, "Message Type = " + ev.getType());
-                    if (DBG) Log.d(TAG, "Message handle = " + ev.getHandle());
+                    if (DBG) {
+                        Log.d(TAG, "Message Type = " + ev.getType());
+                    }
+                    if (DBG) {
+                        Log.d(TAG, "Message handle = " + ev.getHandle());
+                    }
                     switch (ev.getType()) {
 
                         case NEW_MESSAGE:
@@ -480,12 +513,30 @@
             }
         }
 
+        // Sets the specified message status to "read" (from "unread" status, mostly)
+        private void markMessageRead(RequestGetMessage request) {
+            if (DBG) Log.d(TAG, "markMessageRead");
+            mMasClient.makeRequest(new RequestSetMessageStatus(
+                    request.getHandle(), RequestSetMessageStatus.StatusIndicator.READ));
+        }
+
+        // Sets the specified message status to "deleted"
+        private void markMessageDeleted(RequestGetMessage request) {
+            if (DBG) Log.d(TAG, "markMessageDeleted");
+            mMasClient.makeRequest(new RequestSetMessageStatus(
+                    request.getHandle(), RequestSetMessageStatus.StatusIndicator.DELETED));
+        }
+
         private void processMessageListing(RequestGetMessagesListing request) {
-            if (DBG) Log.d(TAG, "processMessageListing");
+            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) {
-                    if (DBG) Log.d(TAG, "getting message ");
+                    if (DBG) {
+                        Log.d(TAG, "getting message ");
+                    }
                     getMessage(handle.getHandle());
                 }
             }
@@ -493,29 +544,42 @@
 
         private void processInboundMessage(RequestGetMessage request) {
             Bmessage message = request.getMessage();
-            if (DBG) Log.d(TAG, "Notify inbound Message" + message);
+            if (DBG) {
+                Log.d(TAG, "Notify inbound Message" + message);
+            }
 
-            if (message == null) return;
+            if (message == null) {
+                return;
+            }
             if (!INBOX_PATH.equalsIgnoreCase(message.getFolder())) {
-                if (DBG) Log.d(TAG, "Ignoring message received in " + message.getFolder() + ".");
+                if (DBG) {
+                    Log.d(TAG, "Ignoring message received in " + message.getFolder() + ".");
+                }
                 return;
             }
             switch (message.getType()) {
                 case SMS_CDMA:
                 case SMS_GSM:
-                    if (DBG) Log.d(TAG, "Body: " + message.getBodyContent());
-                    if (DBG) Log.d(TAG, message.toString());
-                    if (DBG) Log.d(TAG, "Recipients" + message.getRecipients().toString());
+                    if (DBG) {
+                        Log.d(TAG, "Body: " + message.getBodyContent());
+                    }
+                    if (DBG) {
+                        Log.d(TAG, message.toString());
+                    }
+                    if (DBG) {
+                        Log.d(TAG, "Recipients" + message.getRecipients().toString());
+                    }
 
                     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(android.content.Intent.EXTRA_TEXT,
-                            message.getBodyContent());
+                    intent.putExtra(android.content.Intent.EXTRA_TEXT, message.getBodyContent());
                     VCardEntry originator = message.getOriginator();
                     if (originator != null) {
-                        if (DBG) Log.d(TAG, originator.toString());
+                        if (DBG) {
+                            Log.d(TAG, originator.toString());
+                        }
                         List<VCardEntry.PhoneData> phoneData = originator.getPhoneList();
                         if (phoneData != null && phoneData.size() > 0) {
                             String phoneNumber = phoneData.get(0).getNumber();
@@ -540,21 +604,37 @@
         }
 
         private void notifySentMessageStatus(String handle, EventReport.Type status) {
-            if (DBG) Log.d(TAG, "got a status for " + handle + " Status = " + status);
+            if (DBG) {
+                Log.d(TAG, "got a status for " + handle + " Status = " + status);
+            }
             PendingIntent intentToSend = null;
-            if (status == EventReport.Type.SENDING_SUCCESS) {
-                intentToSend = sentReceiptRequested.remove(sentMessageLog.get(handle));
-            } else if (status == EventReport.Type.DELIVERY_SUCCESS) {
-                intentToSend = deliveryReceiptRequested.remove(sentMessageLog.get(handle));
+            // ignore the top-order byte (converted to string) in the handle for now
+            String shortHandle = handle.substring(2);
+            if (status == EventReport.Type.SENDING_FAILURE
+                    || status == EventReport.Type.SENDING_SUCCESS) {
+                intentToSend = mSentReceiptRequested.remove(mSentMessageLog.get(shortHandle));
+            } else if (status == EventReport.Type.DELIVERY_SUCCESS
+                    || status == EventReport.Type.DELIVERY_FAILURE) {
+                intentToSend = mDeliveryReceiptRequested.remove(mSentMessageLog.get(shortHandle));
             }
 
             if (intentToSend != null) {
                 try {
-                    if (DBG) Log.d(TAG, "*******Sending " + intentToSend);
-                    intentToSend.send();
+                    if (DBG) {
+                        Log.d(TAG, "*******Sending " + intentToSend);
+                    }
+                    int result = Activity.RESULT_OK;
+                    if (status == EventReport.Type.SENDING_FAILURE
+                            || status == EventReport.Type.DELIVERY_FAILURE) {
+                        result = SmsManager.RESULT_ERROR_GENERIC_FAILURE;
+                    }
+                    intentToSend.send(result);
                 } catch (PendingIntent.CanceledException e) {
                     Log.w(TAG, "Notification Request Canceled" + e);
                 }
+            } else {
+                Log.e(TAG, "Received a notification on message with handle = "
+                        + handle + ", but it is NOT found in mSentMessageLog! where did it go?");
             }
         }
     }
@@ -562,10 +642,10 @@
     class Disconnecting extends State {
         @Override
         public void enter() {
-            if (DBG) Log.d(TAG, "Enter Disconnecting: " + getCurrentMessage().what);
-            onConnectionStateChanged(mPreviousState,
-                    BluetoothProfile.STATE_DISCONNECTING);
-            mService.unregisterReceiver(mMapReceiver);
+            if (DBG) {
+                Log.d(TAG, "Enter Disconnecting: " + getCurrentMessage().what);
+            }
+            onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTING);
 
             if (mMasClient != null) {
                 mMasClient.makeRequest(new RequestSetNotificationRegistration(false));
@@ -592,8 +672,8 @@
                     break;
 
                 default:
-                    Log.w(TAG, "Unexpected message: " + message.what + " from state:" +
-                        this.getName());
+                    Log.w(TAG, "Unexpected message: " + message.what + " from state:"
+                            + this.getName());
                     return NOT_HANDLED;
             }
             return HANDLED;
@@ -607,43 +687,12 @@
     }
 
     void receiveEvent(EventReport ev) {
-        if (DBG) Log.d(TAG, "Message Type = " + ev.getType());
-        if (DBG) Log.d(TAG, "Message handle = " + ev.getHandle());
-        sendMessage(MSG_NOTIFICATION, ev);
-    }
-
-    private class MapBroadcastReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DBG) Log.d(TAG, "onReceive");
-            String action = intent.getAction();
-            if (DBG) Log.d(TAG, "onReceive: " + action);
-            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
-                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                if (getDevice().equals(device) && getState() == BluetoothProfile.STATE_CONNECTED) {
-                    disconnect(device);
-                }
-            }
-
-            if (BluetoothDevice.ACTION_SDP_RECORD.equals(intent.getAction())) {
-                ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
-                if (DBG) Log.d(TAG, "UUID of SDP: " + uuid);
-
-                if (uuid.equals(BluetoothUuid.MAS)) {
-                    // Check if we have a valid SDP record.
-                    SdpMasRecord masRecord =
-                            intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
-                    if (DBG) Log.d(TAG, "SDP = " + masRecord);
-                    int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
-                    if (masRecord == null) {
-                        Log.w(TAG, "SDP search ended with no MAS record. Status: " + status);
-                        return;
-                    }
-                    obtainMessage(
-                            MceStateMachine.MSG_MAS_SDP_DONE,
-                            masRecord).sendToTarget();
-                }
-            }
+        if (DBG) {
+            Log.d(TAG, "Message Type = " + ev.getType());
         }
+        if (DBG) {
+            Log.d(TAG, "Message handle = " + ev.getHandle());
+        }
+        sendMessage(MSG_NOTIFICATION, ev);
     }
 }
diff --git a/src/com/android/bluetooth/mapclient/MnsObexServer.java b/src/com/android/bluetooth/mapclient/MnsObexServer.java
index 00f3291..53cd79b 100644
--- a/src/com/android/bluetooth/mapclient/MnsObexServer.java
+++ b/src/com/android/bluetooth/mapclient/MnsObexServer.java
@@ -31,20 +31,34 @@
 
 class MnsObexServer extends ServerRequestHandler {
 
-    private final static String TAG = "MnsObexServer";
-    private boolean VDBG = MapClientService.VDBG;
+    private static final String TAG = "MnsObexServer";
+    private static final boolean VDBG = MapClientService.VDBG;
 
     private static final byte[] MNS_TARGET = new byte[]{
-            (byte) 0xbb, 0x58, 0x2b, 0x41, 0x42, 0x0c, 0x11, (byte) 0xdb, (byte) 0xb0, (byte) 0xde,
-            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
+            (byte) 0xbb,
+            0x58,
+            0x2b,
+            0x41,
+            0x42,
+            0x0c,
+            0x11,
+            (byte) 0xdb,
+            (byte) 0xb0,
+            (byte) 0xde,
+            0x08,
+            0x00,
+            0x20,
+            0x0c,
+            (byte) 0x9a,
+            0x66
     };
 
-    private final static String TYPE = "x-bt/MAP-event-report";
+    private static final String TYPE = "x-bt/MAP-event-report";
 
     private final WeakReference<MceStateMachine> mStateMachineReference;
     private final ObexServerSockets mObexServerSockets;
 
-    public MnsObexServer(MceStateMachine stateMachine, ObexServerSockets socketOriginator) {
+    MnsObexServer(MceStateMachine stateMachine, ObexServerSockets socketOriginator) {
         super();
         mStateMachineReference = new WeakReference<>(stateMachine);
         mObexServerSockets = socketOriginator;
@@ -52,8 +66,9 @@
 
     @Override
     public int onConnect(final HeaderSet request, HeaderSet reply) {
-        if (VDBG) Log.v(TAG, "onConnect");
-        mObexServerSockets.prepareForNewConnect();
+        if (VDBG) {
+            Log.v(TAG, "onConnect");
+        }
 
         try {
             byte[] uuid = (byte[]) request.getHeader(HeaderSet.TARGET);
@@ -72,18 +87,24 @@
 
     @Override
     public void onDisconnect(final HeaderSet request, HeaderSet reply) {
-        if (VDBG) Log.v(TAG, "onDisconnect");
+        if (VDBG) {
+            Log.v(TAG, "onDisconnect");
+        }
     }
 
     @Override
     public int onGet(final Operation op) {
-        if (VDBG) Log.v(TAG, "onGet");
+        if (VDBG) {
+            Log.v(TAG, "onGet");
+        }
         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
     }
 
     @Override
     public int onPut(final Operation op) {
-        if (VDBG) Log.v(TAG, "onPut");
+        if (VDBG) {
+            Log.v(TAG, "onPut");
+        }
 
         try {
             HeaderSet headerset;
@@ -112,19 +133,25 @@
 
     @Override
     public int onAbort(final HeaderSet request, HeaderSet reply) {
-        if (VDBG) Log.v(TAG, "onAbort");
+        if (VDBG) {
+            Log.v(TAG, "onAbort");
+        }
         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
     }
 
     @Override
-    public int onSetPath(final HeaderSet request, HeaderSet reply,
-            final boolean backup, final boolean create) {
-        if (VDBG) Log.v(TAG, "onSetPath");
+    public int onSetPath(final HeaderSet request, HeaderSet reply, final boolean backup,
+            final boolean create) {
+        if (VDBG) {
+            Log.v(TAG, "onSetPath");
+        }
         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
     }
 
     @Override
     public void onClose() {
-        if (VDBG) Log.v(TAG, "onClose");
+        if (VDBG) {
+            Log.v(TAG, "onClose");
+        }
     }
 }
diff --git a/src/com/android/bluetooth/mapclient/MnsService.java b/src/com/android/bluetooth/mapclient/MnsService.java
index 1078cf1..c1ab39e 100644
--- a/src/com/android/bluetooth/mapclient/MnsService.java
+++ b/src/com/android/bluetooth/mapclient/MnsService.java
@@ -31,7 +31,10 @@
 
 import javax.obex.ServerSession;
 
-class MnsService {
+/**
+ * Message Notification Server implementation
+ */
+public class MnsService {
     static final int MSG_EVENT = 1;
     /* for Client */
     static final int EVENT_REPORT = 1001;
@@ -40,39 +43,42 @@
     private static final Boolean VDBG = MapClientService.VDBG;
     /* MAP version 1.1 */
     private static final int MNS_VERSION = 0x0101;
-    /* MNS features: Notification Feature */
-    private static final int MNS_FEATURE_BITS = 0x0002;
     /* these are shared across instances */
-    static private SocketAcceptor mAcceptThread = null;
-    static private Handler mSessionHandler = null;
-    static private BluetoothServerSocket mServerSocket = null;
-    static private ObexServerSockets mServerSockets = null;
+    private static SocketAcceptor sAcceptThread = null;
+    private static Handler sSessionHandler = null;
+    private static BluetoothServerSocket sServerSocket = null;
+    private static ObexServerSockets sServerSockets = null;
 
-    static private MapClientService mContext;
+    private static MapClientService sContext;
     private volatile boolean mShutdown = false;         // Used to interrupt socket accept thread
     private int mSdpHandle = -1;
 
     MnsService(MapClientService context) {
-        if (VDBG) Log.v(TAG, "MnsService()");
-        mContext = context;
-        mAcceptThread = new SocketAcceptor();
-        mServerSockets = ObexServerSockets.create(mAcceptThread);
+        if (VDBG) {
+            Log.v(TAG, "MnsService()");
+        }
+        sContext = context;
+        sAcceptThread = new SocketAcceptor();
+        sServerSockets = ObexServerSockets.create(sAcceptThread);
         SdpManager sdpManager = SdpManager.getDefaultManager();
         if (sdpManager == null) {
             Log.e(TAG, "SdpManager is null");
             return;
         }
         mSdpHandle = sdpManager.createMapMnsRecord("MAP Message Notification Service",
-                mServerSockets.getRfcommChannel(), -1, MNS_VERSION, MNS_FEATURE_BITS);
+                sServerSockets.getRfcommChannel(), -1, MNS_VERSION,
+                MasClient.MAP_SUPPORTED_FEATURES);
     }
 
     void stop() {
-        if (VDBG) Log.v(TAG, "stop()");
+        if (VDBG) {
+            Log.v(TAG, "stop()");
+        }
         mShutdown = true;
         cleanUpSdpRecord();
-        if (mServerSockets != null) {
-            mServerSockets.shutdown(false);
-            mServerSockets = null;
+        if (sServerSockets != null) {
+            sServerSockets.shutdown(false);
+            sServerSockets = null;
         }
     }
 
@@ -106,7 +112,7 @@
         @Override
         public synchronized void onAcceptFailed() {
             Log.e(TAG, "OnAcceptFailed");
-            mServerSockets = null; // Will cause a new to be created when calling start.
+            sServerSockets = null; // Will cause a new to be created when calling start.
             if (mShutdown) {
                 Log.e(TAG, "Failed to accept incomming connection - " + "shutdown");
             }
@@ -114,10 +120,17 @@
 
         @Override
         public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
-            if (DBG) Log.d(TAG, "onConnect" + device + " SOCKET: " + socket);
+            if (DBG) {
+                Log.d(TAG, "onConnect" + device + " SOCKET: " + socket);
+            }
             /* Signal to the service that we have received an incoming connection.*/
-            MnsObexServer srv = new MnsObexServer(
-                    mContext.mMceStateMachine, mServerSockets);
+            MceStateMachine stateMachine = sContext.getMceStateMachineForDevice(device);
+            if (stateMachine == null) {
+                Log.e(TAG, "Error: NO statemachine for device: " + device.getAddress()
+                        + " (name: " + device.getName());
+                return false;
+            }
+            MnsObexServer srv = new MnsObexServer(stateMachine, sServerSockets);
             BluetoothObexTransport transport = new BluetoothObexTransport(socket);
             try {
                 new ServerSession(transport, srv, null);
diff --git a/src/com/android/bluetooth/mapclient/obex/BmessageBuilder.java b/src/com/android/bluetooth/mapclient/obex/BmessageBuilder.java
index 36492fc..ba56285 100644
--- a/src/com/android/bluetooth/mapclient/obex/BmessageBuilder.java
+++ b/src/com/android/bluetooth/mapclient/obex/BmessageBuilder.java
@@ -22,35 +22,36 @@
 import com.android.vcard.VCardEntry.PhoneData;
 
 import java.util.List;
+
 /* BMessage as defined by MAP_SPEC_V101 Section 3.1.3 Message format (x-bt/message) */
 class BmessageBuilder {
-    private final static String CRLF = "\r\n";
-    private final static String BMSG_BEGIN = "BEGIN:BMSG";
-    private final static String BMSG_VERSION = "VERSION:1.0";
-    private final static String BMSG_STATUS = "STATUS:";
-    private final static String BMSG_TYPE = "TYPE:";
-    private final static String BMSG_FOLDER = "FOLDER:";
-    private final static String BMSG_END = "END:BMSG";
+    private static final String CRLF = "\r\n";
+    private static final String BMSG_BEGIN = "BEGIN:BMSG";
+    private static final String BMSG_VERSION = "VERSION:1.0";
+    private static final String BMSG_STATUS = "STATUS:";
+    private static final String BMSG_TYPE = "TYPE:";
+    private static final String BMSG_FOLDER = "FOLDER:";
+    private static final String BMSG_END = "END:BMSG";
 
-    private final static String BENV_BEGIN = "BEGIN:BENV";
-    private final static String BENV_END = "END:BENV";
+    private static final String BENV_BEGIN = "BEGIN:BENV";
+    private static final String BENV_END = "END:BENV";
 
-    private final static String BBODY_BEGIN = "BEGIN:BBODY";
-    private final static String BBODY_ENCODING = "ENCODING:";
-    private final static String BBODY_CHARSET = "CHARSET:";
-    private final static String BBODY_LANGUAGE = "LANGUAGE:";
-    private final static String BBODY_LENGTH = "LENGTH:";
-    private final static String BBODY_END = "END:BBODY";
+    private static final String BBODY_BEGIN = "BEGIN:BBODY";
+    private static final String BBODY_ENCODING = "ENCODING:";
+    private static final String BBODY_CHARSET = "CHARSET:";
+    private static final String BBODY_LANGUAGE = "LANGUAGE:";
+    private static final String BBODY_LENGTH = "LENGTH:";
+    private static final String BBODY_END = "END:BBODY";
 
-    private final static String MSG_BEGIN = "BEGIN:MSG";
-    private final static String MSG_END = "END:MSG";
+    private static final String MSG_BEGIN = "BEGIN:MSG";
+    private static final String MSG_END = "END:MSG";
 
-    private final static String VCARD_BEGIN = "BEGIN:VCARD";
-    private final static String VCARD_VERSION = "VERSION:2.1";
-    private final static String VCARD_N = "N:";
-    private final static String VCARD_EMAIL = "EMAIL:";
-    private final static String VCARD_TEL = "TEL:";
-    private final static String VCARD_END = "END:VCARD";
+    private static final String VCARD_BEGIN = "BEGIN:VCARD";
+    private static final String VCARD_VERSION = "VERSION:2.1";
+    private static final String VCARD_N = "N:";
+    private static final String VCARD_EMAIL = "EMAIL:";
+    private static final String VCARD_TEL = "TEL:";
+    private static final String VCARD_END = "END:VCARD";
 
     private final StringBuilder mBmsg;
 
@@ -58,7 +59,7 @@
         mBmsg = new StringBuilder();
     }
 
-    static public String createBmessage(Bmessage bmsg) {
+    public static String createBmessage(Bmessage bmsg) {
         BmessageBuilder b = new BmessageBuilder();
 
         b.build(bmsg);
diff --git a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
index 539f8be..2705e34 100644
--- a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
+++ b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
@@ -35,33 +35,33 @@
 
 /* BMessage as defined by MAP_SPEC_V101 Section 3.1.3 Message format (x-bt/message) */
 class BmessageParser {
-    private final static String TAG = "BmessageParser";
-    private final static boolean DBG = false;
+    private static final String TAG = "BmessageParser";
+    private static final boolean DBG = false;
 
-    private final static String CRLF = "\r\n";
+    private static final String CRLF = "\r\n";
 
-    private final static Property BEGIN_BMSG = new Property("BEGIN", "BMSG");
-    private final static Property END_BMSG = new Property("END", "BMSG");
+    private static final Property BEGIN_BMSG = new Property("BEGIN", "BMSG");
+    private static final Property END_BMSG = new Property("END", "BMSG");
 
-    private final static Property BEGIN_VCARD = new Property("BEGIN", "VCARD");
-    private final static Property END_VCARD = new Property("END", "VCARD");
+    private static final Property BEGIN_VCARD = new Property("BEGIN", "VCARD");
+    private static final Property END_VCARD = new Property("END", "VCARD");
 
-    private final static Property BEGIN_BENV = new Property("BEGIN", "BENV");
-    private final static Property END_BENV = new Property("END", "BENV");
+    private static final Property BEGIN_BENV = new Property("BEGIN", "BENV");
+    private static final Property END_BENV = new Property("END", "BENV");
 
-    private final static Property BEGIN_BBODY = new Property("BEGIN", "BBODY");
-    private final static Property END_BBODY = new Property("END", "BBODY");
+    private static final Property BEGIN_BBODY = new Property("BEGIN", "BBODY");
+    private static final Property END_BBODY = new Property("END", "BBODY");
 
-    private final static Property BEGIN_MSG = new Property("BEGIN", "MSG");
-    private final static Property END_MSG = new Property("END", "MSG");
+    private static final Property BEGIN_MSG = new Property("BEGIN", "MSG");
+    private static final Property END_MSG = new Property("END", "MSG");
 
-    private final static int CRLF_LEN = 2;
+    private static final int CRLF_LEN = 2;
 
     /*
      * length of "container" for 'message' in bmessage-body-content:
      * BEGIN:MSG<CRLF> + <CRLF> + END:MSG<CRFL>
      */
-    private final static int MSG_CONTAINER_LEN = 22;
+    private static final int MSG_CONTAINER_LEN = 22;
     private final Bmessage mBmsg;
     private BmsgTokenizer mParser;
 
@@ -69,7 +69,7 @@
         mBmsg = new Bmessage();
     }
 
-    static public Bmessage createBmessage(String str) {
+    public static Bmessage createBmessage(String str) {
         BmessageParser p = new BmessageParser();
 
         if (DBG) {
@@ -262,6 +262,7 @@
             prop = mParser.next();
 
             if (prop.name.equals("PARTID")) {
+                // Do nothing
             } else if (prop.name.equals("ENCODING")) {
                 mBmsg.mBbodyEncoding = prop.value;
 
@@ -422,7 +423,7 @@
     }
 
     private class VcardHandler implements VCardEntryHandler {
-        VCardEntry vcard;
+        public VCardEntry vcard;
 
         @Override
         public void onStart() {
diff --git a/src/com/android/bluetooth/mapclient/obex/BmsgTokenizer.java b/src/com/android/bluetooth/mapclient/obex/BmsgTokenizer.java
index 4128d2a..0964ae3 100644
--- a/src/com/android/bluetooth/mapclient/obex/BmsgTokenizer.java
+++ b/src/com/android/bluetooth/mapclient/obex/BmsgTokenizer.java
@@ -77,7 +77,7 @@
         return mPos + mOffset;
     }
 
-    static public class Property {
+    public static class Property {
         public final String name;
         public final String value;
 
@@ -100,8 +100,7 @@
         @Override
         public boolean equals(Object o) {
             return ((o instanceof Property) && ((Property) o).name.equals(name)
-                    && ((Property) o).value
-                    .equals(value));
+                    && ((Property) o).value.equals(value));
         }
     }
 }
diff --git a/src/com/android/bluetooth/mapclient/obex/EventReport.java b/src/com/android/bluetooth/mapclient/obex/EventReport.java
index 22f2861..53c0c00 100644
--- a/src/com/android/bluetooth/mapclient/obex/EventReport.java
+++ b/src/com/android/bluetooth/mapclient/obex/EventReport.java
@@ -36,7 +36,7 @@
  * callback message.
  */
 public class EventReport {
-    private final static String TAG = "EventReport";
+    private static final String TAG = "EventReport";
     private final Type mType;
     private final String mHandle;
     private final String mFolder;
@@ -67,7 +67,7 @@
         if (mType != Type.MEMORY_FULL && mType != Type.MEMORY_AVAILABLE) {
             String s = attrs.get("msg_type");
 
-            if ("".equals(s)) {
+            if (s != null && s.isEmpty()) {
                 // Some phones (e.g. SGS3 for MessageDeleted) send empty
                 // msg_type, in such case leave it as null rather than throw
                 // parse exception
@@ -198,15 +198,19 @@
     }
 
     public enum Type {
-        NEW_MESSAGE("NewMessage"), DELIVERY_SUCCESS("DeliverySuccess"),
-        SENDING_SUCCESS("SendingSuccess"), DELIVERY_FAILURE("DeliveryFailure"),
-        SENDING_FAILURE("SendingFailure"), MEMORY_FULL("MemoryFull"),
-        MEMORY_AVAILABLE("MemoryAvailable"), MESSAGE_DELETED("MessageDeleted"),
+        NEW_MESSAGE("NewMessage"),
+        DELIVERY_SUCCESS("DeliverySuccess"),
+        SENDING_SUCCESS("SendingSuccess"),
+        DELIVERY_FAILURE("DeliveryFailure"),
+        SENDING_FAILURE("SendingFailure"),
+        MEMORY_FULL("MemoryFull"),
+        MEMORY_AVAILABLE("MemoryAvailable"),
+        MESSAGE_DELETED("MessageDeleted"),
         MESSAGE_SHIFT("MessageShift");
 
         private final String mSpecName;
 
-        private Type(String specName) {
+        Type(String specName) {
             mSpecName = specName;
         }
 
diff --git a/src/com/android/bluetooth/mapclient/obex/FolderListing.java b/src/com/android/bluetooth/mapclient/obex/FolderListing.java
index b3ab1a6..cd1ecc4 100644
--- a/src/com/android/bluetooth/mapclient/obex/FolderListing.java
+++ b/src/com/android/bluetooth/mapclient/obex/FolderListing.java
@@ -32,7 +32,7 @@
 
     private final ArrayList<String> mFolders;
 
-    public FolderListing(InputStream in) {
+    FolderListing(InputStream in) {
         mFolders = new ArrayList<String>();
 
         parse(in);
diff --git a/src/com/android/bluetooth/mapclient/obex/MessagesFilter.java b/src/com/android/bluetooth/mapclient/obex/MessagesFilter.java
index cb90b39..c2a7f78 100644
--- a/src/com/android/bluetooth/mapclient/obex/MessagesFilter.java
+++ b/src/com/android/bluetooth/mapclient/obex/MessagesFilter.java
@@ -10,33 +10,33 @@
  */
 public final class MessagesFilter {
 
-    public final static byte MESSAGE_TYPE_ALL = 0x00;
-    public final static byte MESSAGE_TYPE_SMS_GSM = 0x01;
-    public final static byte MESSAGE_TYPE_SMS_CDMA = 0x02;
-    public final static byte MESSAGE_TYPE_EMAIL = 0x04;
-    public final static byte MESSAGE_TYPE_MMS = 0x08;
+    public static final byte MESSAGE_TYPE_ALL = 0x00;
+    public static final byte MESSAGE_TYPE_SMS_GSM = 0x01;
+    public static final byte MESSAGE_TYPE_SMS_CDMA = 0x02;
+    public static final byte MESSAGE_TYPE_EMAIL = 0x04;
+    public static final byte MESSAGE_TYPE_MMS = 0x08;
 
-    public final static byte READ_STATUS_ANY = 0x00;
-    public final static byte READ_STATUS_UNREAD = 0x01;
-    public final static byte READ_STATUS_READ = 0x02;
+    public static final byte READ_STATUS_ANY = 0x00;
+    public static final byte READ_STATUS_UNREAD = 0x01;
+    public static final byte READ_STATUS_READ = 0x02;
 
-    public final static byte PRIORITY_ANY = 0x00;
-    public final static byte PRIORITY_HIGH = 0x01;
-    public final static byte PRIORITY_NON_HIGH = 0x02;
+    public static final byte PRIORITY_ANY = 0x00;
+    public static final byte PRIORITY_HIGH = 0x01;
+    public static final byte PRIORITY_NON_HIGH = 0x02;
 
-    byte messageType = MESSAGE_TYPE_ALL;
+    public byte messageType = MESSAGE_TYPE_ALL;
 
-    String periodBegin = null;
+    public String periodBegin = null;
 
-    String periodEnd = null;
+    public String periodEnd = null;
 
-    byte readStatus = READ_STATUS_ANY;
+    public byte readStatus = READ_STATUS_ANY;
 
-    String recipient = null;
+    public String recipient = null;
 
-    String originator = null;
+    public String originator = null;
 
-    byte priority = PRIORITY_ANY;
+    public byte priority = PRIORITY_ANY;
 
     public MessagesFilter() {
     }
@@ -60,7 +60,7 @@
     }
 
     public void setRecipient(String filter) {
-        if ("".equals(filter)) {
+        if (filter != null && filter.isEmpty()) {
             recipient = null;
         } else {
             recipient = filter;
@@ -68,7 +68,7 @@
     }
 
     public void setOriginator(String filter) {
-        if ("".equals(filter)) {
+        if (filter != null && filter.isEmpty()) {
             originator = null;
         } else {
             originator = filter;
diff --git a/src/com/android/bluetooth/mapclient/obex/MessagesListing.java b/src/com/android/bluetooth/mapclient/obex/MessagesListing.java
index 83333a5..d473819 100644
--- a/src/com/android/bluetooth/mapclient/obex/MessagesListing.java
+++ b/src/com/android/bluetooth/mapclient/obex/MessagesListing.java
@@ -33,7 +33,7 @@
 
     private final ArrayList<Message> mMessages;
 
-    public MessagesListing(InputStream in) {
+    MessagesListing(InputStream in) {
         mMessages = new ArrayList<Message>();
 
         parse(in);
diff --git a/src/com/android/bluetooth/mapclient/obex/ObexTime.java b/src/com/android/bluetooth/mapclient/obex/ObexTime.java
index 01a99da..42a32c1 100644
--- a/src/com/android/bluetooth/mapclient/obex/ObexTime.java
+++ b/src/com/android/bluetooth/mapclient/obex/ObexTime.java
@@ -32,10 +32,8 @@
          * match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset
          * +/-hhmm
          */
-        Pattern p = Pattern
-                .compile(
-                        "(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(([+-])(\\d{2})(\\d{2})"
-                                + ")?");
+        Pattern p = Pattern.compile(
+                "(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(([+-])(\\d{2})(\\d{2})" + ")?");
         Matcher m = p.matcher(time);
 
         if (m.matches()) {
@@ -95,9 +93,8 @@
         cal.setTime(mDate);
 
         /* note that months are numbered stating from 0 */
-        return String.format(Locale.US, "%04d%02d%02dT%02d%02d%02d",
-                cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1,
-                cal.get(Calendar.DATE), cal.get(Calendar.HOUR_OF_DAY),
+        return String.format(Locale.US, "%04d%02d%02dT%02d%02d%02d", cal.get(Calendar.YEAR),
+                cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE), cal.get(Calendar.HOUR_OF_DAY),
                 cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND));
     }
 }
diff --git a/src/com/android/bluetooth/mapclient/obex/Request.java b/src/com/android/bluetooth/mapclient/obex/Request.java
index 6dfefed..c39eccd 100644
--- a/src/com/android/bluetooth/mapclient/obex/Request.java
+++ b/src/com/android/bluetooth/mapclient/obex/Request.java
@@ -55,29 +55,29 @@
     protected static final byte[] FILLER_BYTE = {
             0x30
     };
-    protected static byte NOTIFICATION_ON = 0x01;
-    protected static byte NOTIFICATION_OFF = 0x00;
-    protected static byte ATTACHMENT_ON = 0x01;
-    protected static byte ATTACHMENT_OFF = 0x00;
-    protected static byte CHARSET_NATIVE = 0x00;
-    protected static byte CHARSET_UTF8 = 0x01;
-    protected static byte STATUS_INDICATOR_READ = 0x00;
-    protected static byte STATUS_INDICATOR_DELETED = 0x01;
-    protected static byte STATUS_NO = 0x00;
-    protected static byte STATUS_YES = 0x01;
-    protected static byte TRANSPARENT_OFF = 0x00;
-    protected static byte TRANSPARENT_ON = 0x01;
-    protected static byte RETRY_OFF = 0x00;
-    protected static byte RETRY_ON = 0x01;
+    protected static final byte NOTIFICATION_ON = 0x01;
+    protected static final byte NOTIFICATION_OFF = 0x00;
+    protected static final byte ATTACHMENT_ON = 0x01;
+    protected static final byte ATTACHMENT_OFF = 0x00;
+    protected static final byte CHARSET_NATIVE = 0x00;
+    protected static final byte CHARSET_UTF8 = 0x01;
+    protected static final byte STATUS_INDICATOR_READ = 0x00;
+    protected static final byte STATUS_INDICATOR_DELETED = 0x01;
+    protected static final byte STATUS_NO = 0x00;
+    protected static final byte STATUS_YES = 0x01;
+    protected static final byte TRANSPARENT_OFF = 0x00;
+    protected static final byte TRANSPARENT_ON = 0x01;
+    protected static final byte RETRY_OFF = 0x00;
+    protected static final byte RETRY_ON = 0x01;
     protected HeaderSet mHeaderSet;
 
     protected int mResponseCode;
 
-    public Request() {
+    Request() {
         mHeaderSet = new HeaderSet();
     }
 
-    abstract public void execute(ClientSession session) throws IOException;
+    public abstract void execute(ClientSession session) throws IOException;
 
     protected void executeGet(ClientSession session) throws IOException {
         ClientOperation op = null;
@@ -139,7 +139,7 @@
         }
     }
 
-    final public boolean isSuccess() {
+    public final boolean isSuccess() {
         return (mResponseCode == ResponseCodes.OBEX_HTTP_OK);
     }
 
diff --git a/src/com/android/bluetooth/mapclient/obex/RequestGetFolderListing.java b/src/com/android/bluetooth/mapclient/obex/RequestGetFolderListing.java
index 23fb32a..c3d36cc 100644
--- a/src/com/android/bluetooth/mapclient/obex/RequestGetFolderListing.java
+++ b/src/com/android/bluetooth/mapclient/obex/RequestGetFolderListing.java
@@ -22,6 +22,7 @@
 
 import javax.obex.ClientSession;
 import javax.obex.HeaderSet;
+
 /* Get a listing of subdirectories. */
 final class RequestGetFolderListing extends Request {
 
@@ -29,7 +30,7 @@
 
     private FolderListing mResponse = null;
 
-    public RequestGetFolderListing(int maxListCount, int listStartOffset) {
+    RequestGetFolderListing(int maxListCount, int listStartOffset) {
 
         if (maxListCount < 0 || maxListCount > 65535) {
             throw new IllegalArgumentException("maxListCount should be [0..65535]");
diff --git a/src/com/android/bluetooth/mapclient/obex/RequestGetMessage.java b/src/com/android/bluetooth/mapclient/obex/RequestGetMessage.java
index e6919cc..1a62fb6 100644
--- a/src/com/android/bluetooth/mapclient/obex/RequestGetMessage.java
+++ b/src/com/android/bluetooth/mapclient/obex/RequestGetMessage.java
@@ -42,8 +42,7 @@
 
     private Bmessage mBmessage;
 
-    public RequestGetMessage(String handle, MasClient.CharsetType charset,
-            boolean attachment) {
+    RequestGetMessage(String handle, MasClient.CharsetType charset, boolean attachment) {
 
         mHeaderSet.setHeader(HeaderSet.NAME, handle);
 
@@ -52,8 +51,7 @@
         ObexAppParameters oap = new ObexAppParameters();
 
         oap.add(OAP_TAGID_CHARSET,
-                MasClient.CharsetType.UTF_8.equals(charset) ? CHARSET_UTF8
-                        : CHARSET_NATIVE);
+                MasClient.CharsetType.UTF_8.equals(charset) ? CHARSET_UTF8 : CHARSET_NATIVE);
 
         oap.add(OAP_TAGID_ATTACHMENT, attachment ? ATTACHMENT_ON : ATTACHMENT_OFF);
 
diff --git a/src/com/android/bluetooth/mapclient/obex/RequestGetMessagesListing.java b/src/com/android/bluetooth/mapclient/obex/RequestGetMessagesListing.java
index a41b35a..b6dc710 100644
--- a/src/com/android/bluetooth/mapclient/obex/RequestGetMessagesListing.java
+++ b/src/com/android/bluetooth/mapclient/obex/RequestGetMessagesListing.java
@@ -35,9 +35,8 @@
 
     private Date mServerTime = null;
 
-    public RequestGetMessagesListing(String folderName, int parameters,
-            MessagesFilter filter, int subjectLength, int maxListCount,
-            int listStartOffset) {
+    RequestGetMessagesListing(String folderName, int parameters, MessagesFilter filter,
+            int subjectLength, int maxListCount, int listStartOffset) {
         if (subjectLength < 0 || subjectLength > 255) {
             throw new IllegalArgumentException("subjectLength should be [0..255]");
         }
diff --git a/src/com/android/bluetooth/mapclient/obex/RequestPushMessage.java b/src/com/android/bluetooth/mapclient/obex/RequestPushMessage.java
index 28b0df0..bc10939 100644
--- a/src/com/android/bluetooth/mapclient/obex/RequestPushMessage.java
+++ b/src/com/android/bluetooth/mapclient/obex/RequestPushMessage.java
@@ -40,9 +40,8 @@
         mHeaderSet.setHeader(HeaderSet.NAME, folder);
     }
 
-    public RequestPushMessage(String folder, Bmessage msg,
-            CharsetType charset,
-            boolean transparent, boolean retry) {
+    RequestPushMessage(String folder, Bmessage msg, CharsetType charset, boolean transparent,
+            boolean retry) {
         this(folder);
         mMsg = msg;
         ObexAppParameters oap = new ObexAppParameters();
diff --git a/src/com/android/bluetooth/mapclient/obex/RequestSetMessageStatus.java b/src/com/android/bluetooth/mapclient/obex/RequestSetMessageStatus.java
new file mode 100644
index 0000000..9b0123e
--- /dev/null
+++ b/src/com/android/bluetooth/mapclient/obex/RequestSetMessageStatus.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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 java.io.IOException;
+
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+
+final class RequestSetMessageStatus extends Request {
+    public enum StatusIndicator { READ, DELETED }
+
+    private static final String TYPE = "x-bt/messageStatus";
+
+    public RequestSetMessageStatus(String handle, StatusIndicator statusInd) {
+        mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
+        mHeaderSet.setHeader(HeaderSet.NAME, handle);
+
+        ObexAppParameters oap = new ObexAppParameters();
+        oap.add(OAP_TAGID_STATUS_INDICATOR,
+                statusInd == StatusIndicator.READ ? STATUS_INDICATOR_READ
+                                                  : STATUS_INDICATOR_DELETED);
+        oap.add(OAP_TAGID_STATUS_VALUE, STATUS_YES);
+        oap.addToHeaderSet(mHeaderSet);
+    }
+
+    @Override
+    public void execute(ClientSession session) throws IOException {
+        executePut(session, FILLER_BYTE);
+    }
+}
diff --git a/src/com/android/bluetooth/mapclient/obex/RequestSetNotificationRegistration.java b/src/com/android/bluetooth/mapclient/obex/RequestSetNotificationRegistration.java
index 6e34891..8cbae8c 100644
--- a/src/com/android/bluetooth/mapclient/obex/RequestSetNotificationRegistration.java
+++ b/src/com/android/bluetooth/mapclient/obex/RequestSetNotificationRegistration.java
@@ -29,7 +29,7 @@
 
     private final boolean mStatus;
 
-    public RequestSetNotificationRegistration(boolean status) {
+    RequestSetNotificationRegistration(boolean status) {
         mStatus = status;
 
         mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
diff --git a/src/com/android/bluetooth/mapclient/obex/RequestSetPath.java b/src/com/android/bluetooth/mapclient/obex/RequestSetPath.java
index fbaeb92..975a498 100644
--- a/src/com/android/bluetooth/mapclient/obex/RequestSetPath.java
+++ b/src/com/android/bluetooth/mapclient/obex/RequestSetPath.java
@@ -30,14 +30,14 @@
     ;
     String mName;
 
-    public RequestSetPath(String name) {
+    RequestSetPath(String name) {
         mDir = SetPathDir.DOWN;
         mName = name;
 
         mHeaderSet.setHeader(HeaderSet.NAME, name);
     }
 
-    public RequestSetPath(boolean goRoot) {
+    RequestSetPath(boolean goRoot) {
         mHeaderSet.setEmptyNameHeader();
         if (goRoot) {
             mDir = SetPathDir.ROOT;
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java b/src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java
new file mode 100644
index 0000000..4976237
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/AvrcpNativeInterface.java
@@ -0,0 +1,255 @@
+/*
+ * 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.avrcp;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Native Interface to communicate with the JNI layer. This class should never be passed null
+ * data.
+ */
+public class AvrcpNativeInterface {
+    private static final String TAG = "NewAvrcpNativeInterface";
+    private static final boolean DEBUG = true;
+
+    private static AvrcpNativeInterface sInstance;
+    private AvrcpTargetService mAvrcpService;
+
+    static {
+        classInitNative();
+    }
+
+    static AvrcpNativeInterface getInterface() {
+        if (sInstance == null) {
+            sInstance = new AvrcpNativeInterface();
+        }
+
+        return sInstance;
+    }
+
+    void init(AvrcpTargetService service) {
+        d("Init AvrcpNativeInterface");
+        mAvrcpService = service;
+        initNative();
+    }
+
+    void cleanup() {
+        d("Cleanup AvrcpNativeInterface");
+        mAvrcpService = null;
+        cleanupNative();
+    }
+
+    Metadata getCurrentSongInfo() {
+        d("getCurrentSongInfo");
+        if (mAvrcpService == null) {
+            Log.w(TAG, "getCurrentSongInfo(): AvrcpTargetService is null");
+            return null;
+        }
+
+        return mAvrcpService.getCurrentSongInfo();
+    }
+
+    PlayStatus getPlayStatus() {
+        d("getPlayStatus");
+        if (mAvrcpService == null) {
+            Log.w(TAG, "getPlayStatus(): AvrcpTargetService is null");
+            return null;
+        }
+
+        return mAvrcpService.getPlayState();
+    }
+
+    void sendMediaKeyEvent(int keyEvent, boolean pushed) {
+        d("sendMediaKeyEvent: keyEvent=" + keyEvent + " pushed=" + pushed);
+        if (mAvrcpService == null) {
+            Log.w(TAG, "sendMediaKeyEvent(): AvrcpTargetService is null");
+            return;
+        }
+
+        mAvrcpService.sendMediaKeyEvent(keyEvent, pushed);
+    }
+
+    String getCurrentMediaId() {
+        d("getCurrentMediaId");
+        if (mAvrcpService == null) {
+            Log.w(TAG, "getMediaPlayerList(): AvrcpTargetService is null");
+            return "";
+        }
+
+        return mAvrcpService.getCurrentMediaId();
+    }
+
+    List<Metadata> getNowPlayingList() {
+        d("getNowPlayingList");
+        if (mAvrcpService == null) {
+            Log.w(TAG, "getMediaPlayerList(): AvrcpTargetService is null");
+            return null;
+        }
+
+        return mAvrcpService.getNowPlayingList();
+    }
+
+    int getCurrentPlayerId() {
+        d("getCurrentPlayerId");
+        if (mAvrcpService == null) {
+            Log.w(TAG, "getMediaPlayerList(): AvrcpTargetService is null");
+            return -1;
+        }
+
+        return mAvrcpService.getCurrentPlayerId();
+    }
+
+    List<PlayerInfo> getMediaPlayerList() {
+        d("getMediaPlayerList");
+        if (mAvrcpService == null) {
+            Log.w(TAG, "getMediaPlayerList(): AvrcpTargetService is null");
+            return null;
+        }
+
+        return mAvrcpService.getMediaPlayerList();
+    }
+
+    // TODO(apanicke): This shouldn't be named setBrowsedPlayer as it doesn't actually connect
+    // anything internally. It just returns the number of items in the root folder.
+    void setBrowsedPlayer(int playerId) {
+        d("setBrowsedPlayer: playerId=" + playerId);
+        mAvrcpService.getPlayerRoot(playerId, (a, b, c, d) ->
+                setBrowsedPlayerResponse(a, b, c, d));
+    }
+
+    void setBrowsedPlayerResponse(int playerId, boolean success, String rootId, int numItems) {
+        d("setBrowsedPlayerResponse: playerId=" + playerId
+                + " success=" + success
+                + " rootId=" + rootId
+                + " numItems=" + numItems);
+        setBrowsedPlayerResponseNative(playerId, success, rootId, numItems);
+    }
+
+    void getFolderItemsRequest(int playerId, String mediaId) {
+        d("getFolderItemsRequest: playerId=" + playerId + " mediaId=" + mediaId);
+        mAvrcpService.getFolderItems(playerId, mediaId, (a, b) -> getFolderItemsResponse(a, b));
+    }
+
+    void getFolderItemsResponse(String parentId, List<ListItem> items) {
+        d("getFolderItemsResponse: parentId=" + parentId + " items.size=" + items.size());
+        getFolderItemsResponseNative(parentId, items);
+    }
+
+    void sendMediaUpdate(boolean metadata, boolean playStatus, boolean queue) {
+        d("sendMediaUpdate: metadata=" + metadata
+                + " playStatus=" + playStatus
+                + " queue=" + queue);
+        sendMediaUpdateNative(metadata, playStatus, queue);
+    }
+
+    void sendFolderUpdate(boolean availablePlayers, boolean addressedPlayers, boolean uids) {
+        d("sendFolderUpdate: availablePlayers=" + availablePlayers
+                + " addressedPlayers=" + addressedPlayers
+                + " uids=" + uids);
+        sendFolderUpdateNative(availablePlayers, addressedPlayers, uids);
+    }
+
+    void playItem(int playerId, boolean nowPlaying, String mediaId) {
+        d("playItem: playerId=" + playerId + " nowPlaying=" + nowPlaying + " mediaId" + mediaId);
+        if (mAvrcpService == null) {
+            Log.d(TAG, "playItem: AvrcpTargetService is null");
+            return;
+        }
+
+        mAvrcpService.playItem(playerId, nowPlaying, mediaId);
+    }
+
+    boolean connectDevice(String bdaddr) {
+        d("connectDevice: bdaddr=" + bdaddr);
+        return connectDeviceNative(bdaddr);
+    }
+
+    boolean disconnectDevice(String bdaddr) {
+        d("disconnectDevice: bdaddr=" + bdaddr);
+        return disconnectDeviceNative(bdaddr);
+    }
+
+    void setActiveDevice(String bdaddr) {
+        BluetoothDevice device =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bdaddr.toUpperCase());
+        d("setActiveDevice: device=" + device);
+        mAvrcpService.setActiveDevice(device);
+    }
+
+    void deviceConnected(String bdaddr, boolean absoluteVolume) {
+        BluetoothDevice device =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bdaddr.toUpperCase());
+        d("deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);
+        if (mAvrcpService == null) {
+            Log.w(TAG, "deviceConnected: AvrcpTargetService is null");
+            return;
+        }
+
+        mAvrcpService.deviceConnected(device, absoluteVolume);
+    }
+
+    void deviceDisconnected(String bdaddr) {
+        BluetoothDevice device =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bdaddr.toUpperCase());
+        d("deviceDisconnected: device=" + device);
+        if (mAvrcpService == null) {
+            Log.w(TAG, "deviceDisconnected: AvrcpTargetService is null");
+            return;
+        }
+
+        mAvrcpService.deviceDisconnected(device);
+    }
+
+    void sendVolumeChanged(int volume) {
+        d("sendVolumeChanged: volume=" + volume);
+        sendVolumeChangedNative(volume);
+    }
+
+    void setVolume(int volume) {
+        d("setVolume: volume=" + volume);
+        if (mAvrcpService == null) {
+            Log.w(TAG, "setVolume: AvrcpTargetService is null");
+            return;
+        }
+
+        mAvrcpService.setVolume(volume);
+    }
+
+    private static native void classInitNative();
+    private native void initNative();
+    private native void sendMediaUpdateNative(
+            boolean trackChanged, boolean playState, boolean playPos);
+    private native void sendFolderUpdateNative(
+            boolean availablePlayers, boolean addressedPlayers, boolean uids);
+    private native void setBrowsedPlayerResponseNative(
+            int playerId, boolean success, String rootId, int numItems);
+    private native void getFolderItemsResponseNative(String parentId, List<ListItem> list);
+    private native void cleanupNative();
+    private native boolean connectDeviceNative(String bdaddr);
+    private native boolean disconnectDeviceNative(String bdaddr);
+    private native void sendVolumeChangedNative(int volume);
+
+    private static void d(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java b/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
new file mode 100644
index 0000000..c7029ce
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/AvrcpTargetService.java
@@ -0,0 +1,384 @@
+/*
+ * 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.avrcp;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.IBluetoothAvrcpTarget;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.btservice.MetricsLogger;
+import com.android.bluetooth.btservice.ProfileService;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides Bluetooth AVRCP Target profile as a service in the Bluetooth application.
+ * @hide
+ */
+public class AvrcpTargetService extends ProfileService {
+    private static final String TAG = "NewAvrcpTargetService";
+    private static final boolean DEBUG = true;
+    private static final String AVRCP_ENABLE_PROPERTY = "persist.bluetooth.enablenewavrcp";
+
+    private static final int AVRCP_MAX_VOL = 127;
+    private static int sDeviceMaxVolume = 0;
+
+    private MediaPlayerList mMediaPlayerList;
+    private AudioManager mAudioManager;
+    private AvrcpBroadcastReceiver mReceiver;
+    private AvrcpNativeInterface mNativeInterface;
+    private AvrcpVolumeManager mVolumeManager;
+
+    // Only used to see if the metadata has changed from its previous value
+    private MediaData mCurrentData;
+
+    private static AvrcpTargetService sInstance = null;
+
+    class ListCallback implements MediaPlayerList.MediaUpdateCallback,
+            MediaPlayerList.FolderUpdateCallback {
+        @Override
+        public void run(MediaData data) {
+            boolean metadata = !Objects.equals(mCurrentData.metadata, data.metadata);
+            boolean state = !MediaPlayerWrapper.playstateEquals(mCurrentData.state, data.state);
+            boolean queue = !Objects.equals(mCurrentData.queue, data.queue);
+
+            if (DEBUG) {
+                Log.d(TAG, "onMediaUpdated: track_changed=" + metadata
+                        + " state=" + state + " queue=" + queue);
+            }
+            mCurrentData = data;
+
+            mNativeInterface.sendMediaUpdate(metadata, state, queue);
+        }
+
+        @Override
+        public void run(boolean availablePlayers, boolean addressedPlayers,
+                boolean uids) {
+            mNativeInterface.sendFolderUpdate(availablePlayers, addressedPlayers, uids);
+        }
+    }
+
+    private class AvrcpBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
+                if (mNativeInterface == null) return;
+
+                // Update all the playback status info for each connected device
+                mNativeInterface.sendMediaUpdate(false, true, false);
+            }
+        }
+    }
+
+    /**
+     * Get the AvrcpTargetService instance. Returns null if the service hasn't been initialized.
+     */
+    public static AvrcpTargetService get() {
+        return sInstance;
+    }
+
+    @Override
+    public String getName() {
+        return TAG;
+    }
+
+    @Override
+    protected IProfileServiceBinder initBinder() {
+        return new AvrcpTargetBinder(this);
+    }
+
+    @Override
+    protected void setUserUnlocked(int userId) {
+        Log.i(TAG, "User unlocked, initializing the service");
+
+        if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) {
+            Log.w(TAG, "Skipping initialization of the new AVRCP Target Player List");
+            sInstance = null;
+            return;
+        }
+
+        if (mMediaPlayerList != null) {
+            mMediaPlayerList.init(new ListCallback());
+        }
+    }
+
+    @Override
+    protected boolean start() {
+        if (sInstance != null) {
+            Log.wtfStack(TAG, "The service has already been initialized");
+            return false;
+        }
+
+        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;
+            return true;
+        }
+
+        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+
+        mMediaPlayerList = new MediaPlayerList(Looper.myLooper(), this);
+
+        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);
+
+        // Only allow the service to be used once it is initialized
+        sInstance = this;
+
+        return true;
+    }
+
+    @Override
+    protected boolean stop() {
+        Log.i(TAG, "Stopping the AVRCP Target Service");
+
+        if (sInstance == null) {
+            Log.w(TAG, "stop() called before start()");
+            return true;
+        }
+
+        sInstance = null;
+        unregisterReceiver(mReceiver);
+
+        // We check the interfaces first since they only get set on User Unlocked
+        if (mMediaPlayerList != null) mMediaPlayerList.cleanup();
+        if (mNativeInterface != null) mNativeInterface.cleanup();
+
+        mMediaPlayerList = null;
+        mNativeInterface = null;
+        mAudioManager = null;
+        mReceiver = null;
+        return true;
+    }
+
+    private void init() {
+    }
+
+    void deviceConnected(BluetoothDevice device, boolean absoluteVolume) {
+        Log.i(TAG, "deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);
+        mVolumeManager.deviceConnected(device, absoluteVolume);
+        MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.AVRCP);
+    }
+
+    void deviceDisconnected(BluetoothDevice device) {
+        Log.i(TAG, "deviceDisconnected: device=" + device);
+        mVolumeManager.deviceDisconnected(device);
+    }
+
+    /**
+     * Signal to the service that the current audio out device has changed and to inform
+     * the audio service whether the new device supports absolute volume. If it does, also
+     * set the absolute volume level on the remote device.
+     */
+    public void volumeDeviceSwitched(BluetoothDevice device) {
+        if (DEBUG) {
+            Log.d(TAG, "volumeDeviceSwitched: device=" + device);
+        }
+        mVolumeManager.volumeDeviceSwitched(device);
+    }
+
+    /**
+     * Store the current system volume for a device in order to be retrieved later.
+     */
+    public void storeVolumeForDevice(BluetoothDevice device) {
+        if (device == null) return;
+
+        mVolumeManager.storeVolumeForDevice(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);
+    }
+
+    // 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);
+        }
+        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, deviceVolume,
+                AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+    }
+
+    /**
+     * Set the volume on the remote device. Does nothing if the device doesn't support absolute
+     * 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);
+        }
+        mNativeInterface.sendVolumeChanged(avrcpVolume);
+    }
+
+    Metadata getCurrentSongInfo() {
+        return mMediaPlayerList.getCurrentSongInfo();
+    }
+
+    PlayStatus getPlayState() {
+        return PlayStatus.fromPlaybackState(mMediaPlayerList.getCurrentPlayStatus(),
+                Long.parseLong(getCurrentSongInfo().duration));
+    }
+
+    String getCurrentMediaId() {
+        String id = mMediaPlayerList.getCurrentMediaId();
+        if (id != null) return id;
+
+        Metadata song = getCurrentSongInfo();
+        if (song != null) return song.mediaId;
+
+        // We always want to return something, the error string just makes debugging easier
+        return "error";
+    }
+
+    List<Metadata> getNowPlayingList() {
+        return mMediaPlayerList.getNowPlayingList();
+    }
+
+    int getCurrentPlayerId() {
+        return mMediaPlayerList.getCurrentPlayerId();
+    }
+
+    // TODO (apanicke): Have the Player List also contain info about the play state of each player
+    List<PlayerInfo> getMediaPlayerList() {
+        return mMediaPlayerList.getMediaPlayerList();
+    }
+
+    void getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb) {
+        mMediaPlayerList.getPlayerRoot(playerId, cb);
+    }
+
+    void getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb) {
+        mMediaPlayerList.getFolderItems(playerId, mediaId, cb);
+    }
+
+    void playItem(int playerId, boolean nowPlaying, String mediaId) {
+        // NOTE: playerId isn't used if nowPlaying is true, since its assumed to be the current
+        // active player
+        mMediaPlayerList.playItem(playerId, nowPlaying, mediaId);
+    }
+
+    // TODO (apanicke): Handle key events here in the service. Currently it was more convenient to
+    // handle them there but logically they make more sense handled here.
+    void sendMediaKeyEvent(int event, boolean pushed) {
+        if (DEBUG) Log.d(TAG, "getMediaKeyEvent: event=" + event + " pushed=" + pushed);
+        mMediaPlayerList.sendMediaKeyEvent(event, pushed);
+    }
+
+    void setActiveDevice(BluetoothDevice device) {
+        Log.i(TAG, "setActiveDevice: device=" + device);
+        if (device == null) {
+            Log.wtfStack(TAG, "setActiveDevice: could not find device " + device);
+        }
+        A2dpService.getA2dpService().setActiveDevice(device);
+    }
+
+    /**
+     * Dump debugging information to the string builder
+     */
+    public void dump(StringBuilder sb) {
+        sb.append("\nProfile: AvrcpTargetService:\n");
+        if (sInstance == null) {
+            sb.append("AvrcpTargetService not running");
+            return;
+        }
+
+        StringBuilder tempBuilder = new StringBuilder();
+        if (mMediaPlayerList != null) {
+            mMediaPlayerList.dump(tempBuilder);
+        } else {
+            tempBuilder.append("\nMedia Player List is empty\n");
+        }
+
+        mVolumeManager.dump(tempBuilder);
+
+        // Tab everything over by two spaces
+        sb.append(tempBuilder.toString().replaceAll("(?m)^", "  "));
+    }
+
+    private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub
+            implements IProfileServiceBinder {
+        private AvrcpTargetService mService;
+
+        AvrcpTargetBinder(AvrcpTargetService service) {
+            mService = service;
+        }
+
+        @Override
+        public void cleanup() {
+            mService = null;
+        }
+
+        @Override
+        public void sendVolumeChanged(int volume) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "sendVolumeChanged not allowed for non-active user");
+                return;
+            }
+
+            if (mService == null) {
+                return;
+            }
+
+            mService.sendVolumeChanged(volume);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java b/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java
new file mode 100644
index 0000000..2e2ce42
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/AvrcpVolumeManager.java
@@ -0,0 +1,240 @@
+/*
+ * 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.avrcp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+class AvrcpVolumeManager extends AudioDeviceCallback {
+    public static final String TAG = "NewAvrcpVolumeManager";
+    public static final boolean DEBUG = true;
+
+    // All volumes are stored at system volume values, not AVRCP values
+    private static final String VOLUME_MAP = "bluetooth_volume_map";
+    private static final String VOLUME_BLACKLIST = "absolute_volume_blacklist";
+    private static final int AVRCP_MAX_VOL = 127;
+    private static final int STREAM_MUSIC = AudioManager.STREAM_MUSIC;
+    private static int sDeviceMaxVolume = 0;
+    private static int sNewDeviceVolume = 0;
+
+    Context mContext;
+    AudioManager mAudioManager;
+    AvrcpNativeInterface mNativeInterface;
+
+    HashMap<BluetoothDevice, Boolean> mDeviceMap = new HashMap();
+    HashMap<BluetoothDevice, Integer> mVolumeMap = new HashMap();
+    BluetoothDevice mCurrentDevice = null;
+    boolean mAbsoluteVolumeSupported = false;
+
+    static int avrcpToSystemVolume(int avrcpVolume) {
+        return (int) Math.floor((double) avrcpVolume * sDeviceMaxVolume / AVRCP_MAX_VOL);
+    }
+
+    static int systemToAvrcpVolume(int deviceVolume) {
+        int avrcpVolume = (int) Math.floor((double) deviceVolume
+                * AVRCP_MAX_VOL / sDeviceMaxVolume);
+        if (avrcpVolume > 127) avrcpVolume = 127;
+        return avrcpVolume;
+    }
+
+    private SharedPreferences getVolumeMap() {
+        return mContext.getSharedPreferences(VOLUME_MAP, Context.MODE_PRIVATE);
+    }
+
+    private void switchVolumeDevice(@NonNull BluetoothDevice device) {
+        // Inform the audio manager that the device has changed
+        d("switchVolumeDevice: Set Absolute volume support to " + mDeviceMap.get(device));
+        mAudioManager.avrcpSupportsAbsoluteVolume(device.getAddress(), mDeviceMap.get(device));
+
+        // Get the current system volume and try to get the preference volume
+        int savedVolume = getVolume(device, sNewDeviceVolume);
+
+        d("switchVolumeDevice: savedVolume=" + savedVolume);
+
+        // If absolute volume for the device is supported, set the volume for the device
+        if (mDeviceMap.get(device)) {
+            int avrcpVolume = systemToAvrcpVolume(savedVolume);
+            Log.i(TAG, "switchVolumeDevice: Updating device volume: avrcpVolume=" + avrcpVolume);
+            mNativeInterface.sendVolumeChanged(avrcpVolume);
+        }
+    }
+
+    AvrcpVolumeManager(Context context, AudioManager audioManager,
+            AvrcpNativeInterface nativeInterface) {
+        mContext = context;
+        mAudioManager = audioManager;
+        mNativeInterface = nativeInterface;
+        sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        sNewDeviceVolume = sDeviceMaxVolume / 2;
+
+        mAudioManager.registerAudioDeviceCallback(this, null);
+
+        // Load the stored volume preferences into a hash map since shared preferences are slow
+        // to poll and update. If the device has been unbonded since last start remove it from
+        // the map.
+        Map<String, ?> allKeys = getVolumeMap().getAll();
+        SharedPreferences.Editor volumeMapEditor = getVolumeMap().edit();
+        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            BluetoothDevice d = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(key);
+
+            if (value instanceof Integer && d.getBondState() == BluetoothDevice.BOND_BONDED) {
+                mVolumeMap.put(d, (Integer) value);
+            } else {
+                d("Removing " + key + " from the volume map");
+                volumeMapEditor.remove(key);
+            }
+        }
+        volumeMapEditor.apply();
+    }
+
+    void storeVolumeForDevice(BluetoothDevice device) {
+        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) {
+        if (!mVolumeMap.containsKey(device)) {
+            Log.w(TAG, "getVolume: Couldn't find volume preference for device: " + device);
+            return defaultValue;
+        }
+
+        d("getVolume: Returning volume " + mVolumeMap.get(device));
+        return mVolumeMap.get(device);
+    }
+
+    @Override
+    public synchronized void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+        if (mCurrentDevice == null) {
+            d("onAudioDevicesAdded: Not expecting device changed");
+            return;
+        }
+
+        boolean foundDevice = false;
+        d("onAudioDevicesAdded: size: " + addedDevices.length);
+        for (int i = 0; i < addedDevices.length; i++) {
+            d("onAudioDevicesAdded: address=" + addedDevices[i].getAddress());
+            if (addedDevices[i].getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
+                    && Objects.equals(addedDevices[i].getAddress(), mCurrentDevice.getAddress())) {
+                foundDevice = true;
+                break;
+            }
+        }
+
+        if (!foundDevice) {
+            d("Didn't find deferred device in list: device=" + mCurrentDevice);
+            return;
+        }
+
+        // A2DP can sometimes connect and set a device to active before AVRCP has determined if the
+        // device supports absolute volume. Defer switching the device until AVRCP returns the
+        // info.
+        if (!mDeviceMap.containsKey(mCurrentDevice)) {
+            Log.w(TAG, "volumeDeviceSwitched: Device isn't connected: " + mCurrentDevice);
+            return;
+        }
+
+        switchVolumeDevice(mCurrentDevice);
+    }
+
+    synchronized void deviceConnected(@NonNull BluetoothDevice device, boolean absoluteVolume) {
+        d("deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);
+
+        mDeviceMap.put(device, absoluteVolume);
+
+        // AVRCP features lookup has completed after the device became active. Switch to the new
+        // device now.
+        if (device.equals(mCurrentDevice)) {
+            switchVolumeDevice(device);
+        }
+    }
+
+    synchronized void volumeDeviceSwitched(@Nullable BluetoothDevice device) {
+        d("volumeDeviceSwitched: mCurrentDevice=" + mCurrentDevice + " device=" + device);
+
+        if (Objects.equals(device, mCurrentDevice)) {
+            return;
+        }
+
+        // Wait until AudioManager informs us that the new device is connected
+        mCurrentDevice = device;
+    }
+
+    synchronized void deviceDisconnected(@NonNull BluetoothDevice device) {
+        d("deviceDisconnected: device=" + device);
+        mDeviceMap.remove(device);
+    }
+
+    public void dump(StringBuilder sb) {
+        sb.append("AvrcpVolumeManager:\n");
+        sb.append("  mCurrentDevice: " + mCurrentDevice + "\n");
+        sb.append("  Current System Volume: " + mAudioManager.getStreamVolume(STREAM_MUSIC) + "\n");
+        sb.append("  Device Volume Memory Map:\n");
+        sb.append(String.format("    %-17s : %-14s : %3s : %s\n",
+                "Device Address", "Device Name", "Vol", "AbsVol"));
+        Map<String, ?> allKeys = getVolumeMap().getAll();
+        for (Map.Entry<String, ?> entry : allKeys.entrySet()) {
+            Object value = entry.getValue();
+            BluetoothDevice d = BluetoothAdapter.getDefaultAdapter()
+                    .getRemoteDevice(entry.getKey());
+
+            String deviceName = d.getName();
+            if (deviceName == null) {
+                deviceName = "";
+            } else if (deviceName.length() > 14) {
+                deviceName = deviceName.substring(0, 11).concat("...");
+            }
+
+            String absoluteVolume = "NotConnected";
+            if (mDeviceMap.containsKey(d)) {
+                absoluteVolume = mDeviceMap.get(d).toString();
+            }
+
+            if (value instanceof Integer) {
+                sb.append(String.format("    %-17s : %-14s : %3d : %s\n",
+                        d.getAddress(), deviceName, (Integer) value, absoluteVolume));
+            }
+        }
+    }
+
+    static void d(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java
new file mode 100644
index 0000000..ab23dbf
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/BrowsablePlayerConnector.java
@@ -0,0 +1,172 @@
+/*
+ * 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.avrcp;
+
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class provides a way to connect to multiple browsable players at a time.
+ * It will attempt to simultaneously connect to a list of services that support
+ * the MediaBrowserService. After a timeout, the list of connected players will
+ * be returned via callback.
+ *
+ * The main use of this class is to check whether a player can be browsed despite
+ * using the MediaBrowserService. This way we do not have to do the same checks
+ * when constructing BrowsedPlayerWrappers by hand.
+ */
+public class BrowsablePlayerConnector {
+    private static final String TAG = "NewAvrcpBrowsablePlayerConnector";
+    private static final boolean DEBUG = true;
+    private static final long CONNECT_TIMEOUT_MS = 10000; // Time in ms to wait for a connection
+
+    private static final int MSG_GET_FOLDER_ITEMS_CB = 0;
+    private static final int MSG_CONNECT_CB = 1;
+    private static final int MSG_TIMEOUT = 2;
+
+    private Handler mHandler;
+    private Context mContext;
+    private PlayerListCallback mCallback;
+
+    private List<BrowsedPlayerWrapper> mResults = new ArrayList<BrowsedPlayerWrapper>();
+    private Set<BrowsedPlayerWrapper> mPendingPlayers = new HashSet<BrowsedPlayerWrapper>();
+
+    interface PlayerListCallback {
+        void run(List<BrowsedPlayerWrapper> result);
+    }
+
+    static BrowsablePlayerConnector connectToPlayers(
+            Context context,
+            Looper looper,
+            List<ResolveInfo> players,
+            PlayerListCallback cb) {
+        if (cb == null) {
+            Log.wtfStack(TAG, "Null callback passed");
+            return null;
+        }
+
+        BrowsablePlayerConnector newWrapper = new BrowsablePlayerConnector(context, looper, cb);
+
+        // Try to start connecting all the browsed player wrappers
+        for (ResolveInfo info : players) {
+            BrowsedPlayerWrapper player = BrowsedPlayerWrapper.wrap(
+                            context,
+                            info.serviceInfo.packageName,
+                            info.serviceInfo.name);
+            newWrapper.mPendingPlayers.add(player);
+            player.connect((int status, BrowsedPlayerWrapper wrapper) -> {
+                // Use the handler to avoid concurrency issues
+                if (DEBUG) {
+                    Log.d(TAG, "Browse player callback called: package="
+                            + info.serviceInfo.packageName
+                            + " : status=" + status);
+                }
+                Message msg = newWrapper.mHandler.obtainMessage(MSG_CONNECT_CB);
+                msg.arg1 = status;
+                msg.obj = wrapper;
+                newWrapper.mHandler.sendMessage(msg);
+            });
+        }
+
+        Message msg = newWrapper.mHandler.obtainMessage(MSG_TIMEOUT);
+        newWrapper.mHandler.sendMessageDelayed(msg, CONNECT_TIMEOUT_MS);
+        return newWrapper;
+    }
+
+    private BrowsablePlayerConnector(Context context, Looper looper, PlayerListCallback cb) {
+        mContext = context;
+        mCallback = cb;
+        mHandler = new Handler(looper) {
+            public void handleMessage(Message msg) {
+                if (DEBUG) Log.d(TAG, "Received a message: msg.what=" + msg.what);
+                switch(msg.what) {
+                    case MSG_GET_FOLDER_ITEMS_CB: {
+                        BrowsedPlayerWrapper wrapper = (BrowsedPlayerWrapper) msg.obj;
+                        // If we failed to remove the wrapper from the pending set, that
+                        // means a timeout occurred and the callback was triggered afterwards
+                        if (!mPendingPlayers.remove(wrapper)) {
+                            return;
+                        }
+
+                        Log.i(TAG, "Successfully added package to results: "
+                                + wrapper.getPackageName());
+                        mResults.add(wrapper);
+                    } break;
+
+                    case MSG_CONNECT_CB: {
+                        BrowsedPlayerWrapper wrapper = (BrowsedPlayerWrapper) msg.obj;
+
+                        if (msg.arg1 != BrowsedPlayerWrapper.STATUS_SUCCESS) {
+                            Log.i(TAG, wrapper.getPackageName() + " is not browsable");
+                            mPendingPlayers.remove(wrapper);
+                            return;
+                        }
+
+                        // Check to see if the root folder has any items
+                        if (DEBUG) {
+                            Log.i(TAG, "Checking root contents for " + wrapper.getPackageName());
+                        }
+                        wrapper.getFolderItems(wrapper.getRootId(),
+                                (int status, String mediaId, List<ListItem> results) -> {
+                                    if (status != BrowsedPlayerWrapper.STATUS_SUCCESS) {
+                                        mPendingPlayers.remove(wrapper);
+                                        return;
+                                    }
+
+                                    if (results.size() == 0) {
+                                        mPendingPlayers.remove(wrapper);
+                                        return;
+                                    }
+
+                                    // Send the response as a message so that it is properly
+                                    // synchronized
+                                    Message success =
+                                            mHandler.obtainMessage(MSG_GET_FOLDER_ITEMS_CB);
+                                    success.obj = wrapper;
+                                    mHandler.sendMessage(success);
+                                });
+                    } break;
+
+                    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();
+                    } break;
+                }
+
+                if (mPendingPlayers.size() == 0) {
+                    Log.i(TAG, "Successfully connected to "
+                            + mResults.size() + " browsable players.");
+                    removeMessages(MSG_TIMEOUT);
+                    mCallback.run(mResults);
+                }
+            }
+        };
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/BrowsedPlayerWrapper.java b/src/com/android/bluetooth/newavrcp/BrowsedPlayerWrapper.java
new file mode 100644
index 0000000..93afd17
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/BrowsedPlayerWrapper.java
@@ -0,0 +1,316 @@
+/*
+ * 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.avrcp;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * Helper class to create an abstraction layer for the MediaBrowser service that AVRCP can use.
+ *
+ * TODO (apanicke): Add timeouts in case a browser takes forever to connect or gets stuck.
+ * Right now this is ok because the BrowsablePlayerConnector will handle timeouts.
+ */
+class BrowsedPlayerWrapper {
+    private static final String TAG = "NewAvrcpBrowsedPlayerWrapper";
+    private static final boolean DEBUG = true;
+
+    enum ConnectionState {
+        DISCONNECTED,
+        CONNECTING,
+        CONNECTED,
+    }
+
+    interface ConnectionCallback {
+        void run(int status, BrowsedPlayerWrapper wrapper);
+    }
+
+    interface BrowseCallback {
+        void run(int status, String mediaId, List<ListItem> results);
+    }
+
+    public static final int STATUS_SUCCESS = 0;
+    public static final int STATUS_CONN_ERROR = 1;
+    public static final int STATUS_LOOKUP_ERROR = 2;
+
+    private MediaBrowser mWrappedBrowser;
+
+    // TODO (apanicke): Store the context in the factories so that we don't need to save this.
+    // As long as the service is alive those factories will have a valid context.
+    private Context mContext;
+    private String mPackageName;
+    private ConnectionCallback mCallback;
+
+    // TODO(apanicke): We cache this because normally you can only grab the root
+    // while connected. We shouldn't cache this since theres nothing in the framework documentation
+    // that says this can't change between connections. Instead always treat empty string as root.
+    private String mRoot = "";
+
+    // A linked hash map that keeps the contents of the last X browsed folders.
+    //
+    // NOTE: This is needed since some carkits will repeatedly request each item in a folder
+    // individually, incrementing the index of the requested item by one at a time. Going through
+    // the subscription process for each individual item is incredibly slow so we cache the items
+    // in the folder in order to speed up the process. We still run the risk of one device pushing
+    // out a cached folder that another device was using, but this is highly unlikely since for
+    // this to happen you would need to be connected to two carkits at the same time.
+    //
+    // TODO (apanicke): Dynamically set the number of cached folders equal to the max number
+    // of connected devices because that is the maximum number of folders that can be browsed at
+    // a single time.
+    static final int NUM_CACHED_FOLDERS = 5;
+    LinkedHashMap<String, List<ListItem>> mCachedFolders =
+            new LinkedHashMap<String, List<ListItem>>(NUM_CACHED_FOLDERS) {
+                @Override
+                protected boolean removeEldestEntry(Map.Entry<String, List<ListItem>> eldest) {
+                    return size() > NUM_CACHED_FOLDERS;
+                }
+            };
+
+    // TODO (apanicke): Investigate if there is a way to create this just by passing in the
+    // MediaBrowser. Right now there is no obvious way to create the browser then update the
+    // connection callback without being forced to re-create the object every time.
+    private BrowsedPlayerWrapper(Context context, String packageName, String className) {
+        mContext = context;
+        mPackageName = packageName;
+
+        mWrappedBrowser = MediaBrowserFactory.make(
+                context,
+                new ComponentName(packageName, className),
+                new MediaConnectionCallback(),
+                null);
+    }
+
+    static BrowsedPlayerWrapper wrap(Context context, String packageName, String className) {
+        Log.i(TAG, "Wrapping Media Browser " + packageName);
+        BrowsedPlayerWrapper wrapper =
+                new BrowsedPlayerWrapper(context, packageName, className);
+        return wrapper;
+    }
+
+    void connect(ConnectionCallback cb) {
+        if (cb == null) {
+            Log.wtfStack(TAG, "connect: Trying to connect to " + mPackageName
+                    + "with null callback");
+        }
+        if (mCallback != null) {
+            Log.w(TAG, "connect: Already trying to connect to " + mPackageName);
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, "connect: Connecting to browsable player: " + mPackageName);
+        mCallback = (int status, BrowsedPlayerWrapper wrapper) -> {
+            cb.run(status, wrapper);
+            wrapper.disconnect();
+        };
+        mWrappedBrowser.connect();
+    }
+
+    void disconnect() {
+        if (DEBUG) Log.d(TAG, "disconnect: Disconnecting from " + mPackageName);
+        mWrappedBrowser.disconnect();
+        mCallback = null;
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public String getRootId() {
+        return mRoot;
+    }
+
+    public void playItem(String mediaId) {
+        if (DEBUG) Log.d(TAG, "playItem: Play Item from media ID: " + mediaId);
+        connect((int status, BrowsedPlayerWrapper wrapper) -> {
+            if (DEBUG) Log.d(TAG, "playItem: Connected to browsable player " + mPackageName);
+
+            MediaController controller = MediaControllerFactory.make(mContext,
+                    wrapper.mWrappedBrowser.getSessionToken());
+            MediaController.TransportControls ctrl = controller.getTransportControls();
+            Log.i(TAG, "playItem: Playing " + mediaId);
+            ctrl.playFromMediaId(mediaId, null);
+        });
+        return;
+    }
+
+    // Returns false if the player is in the connecting state. Wait for it to either be
+    // connected or disconnected.
+    //
+    // TODO (apanicke): Determine what happens when we subscribe to the same item while a
+    // callback is in flight.
+    //
+    // TODO (apanicke): Currently we do a full folder lookup even if the remote device requests
+    // info for only one item. Add a lookup function that can handle getting info for a single
+    // item.
+    public boolean getFolderItems(String mediaId, BrowseCallback cb) {
+        if (mCachedFolders.containsKey(mediaId)) {
+            Log.i(TAG, "getFolderItems: Grabbing cached data for mediaId: " + mediaId);
+            cb.run(STATUS_SUCCESS, mediaId, Util.cloneList(mCachedFolders.get(mediaId)));
+            return true;
+        }
+
+        if (cb == null) {
+            Log.wtfStack(TAG, "connect: Trying to connect to " + mPackageName
+                    + "with null callback");
+        }
+        if (mCallback != null) {
+            Log.w(TAG, "connect: Already trying to connect to " + mPackageName);
+            return false;
+        }
+
+        if (DEBUG) Log.d(TAG, "connect: Connecting to browsable player: " + mPackageName);
+        mCallback = (int status, BrowsedPlayerWrapper wrapper) -> {
+            Log.i(TAG, "getFolderItems: Connected to browsable player: " + mPackageName);
+            if (status != STATUS_SUCCESS) {
+                cb.run(status, "", new ArrayList<ListItem>());
+            }
+
+            // This will disconnect when the callback is called
+            getFolderItemsInternal(mediaId, cb);
+        };
+        mWrappedBrowser.connect();
+
+        return true;
+    }
+
+    // Internal function to call once the Browser is connected
+    private boolean getFolderItemsInternal(String mediaId, BrowseCallback cb) {
+        mWrappedBrowser.subscribe(mediaId, new BrowserSubscriptionCallback(cb));
+        return true;
+    }
+
+    class MediaConnectionCallback extends MediaBrowser.ConnectionCallback {
+        @Override
+        public void onConnected() {
+            Log.i(TAG, "onConnected: " + mPackageName + " is connected");
+            // Get the root while connected because we may need to use it when disconnected.
+            mRoot = mWrappedBrowser.getRoot();
+
+            if (mCallback == null) return;
+
+            if (mRoot == null || mRoot.isEmpty()) {
+                mCallback.run(STATUS_CONN_ERROR, BrowsedPlayerWrapper.this);
+                return;
+            }
+
+            mCallback.run(STATUS_SUCCESS, BrowsedPlayerWrapper.this);
+            mCallback = null;
+        }
+
+
+        @Override
+        public void onConnectionFailed() {
+            Log.w(TAG, "onConnectionFailed: Connection Failed with " + mPackageName);
+            if (mCallback != null) mCallback.run(STATUS_CONN_ERROR, BrowsedPlayerWrapper.this);
+            mCallback = null;
+        }
+
+        // TODO (apanicke): Add a check to list a player as unbrowsable if it suspends immediately
+        // after connection.
+        @Override
+        public void onConnectionSuspended() {
+            mWrappedBrowser.disconnect();
+            Log.i(TAG, "onConnectionSuspended: Connection Suspended with " + mPackageName);
+        }
+    }
+
+    /**
+     * Subscription callback handler. Subscribe to a folder to get its contents. We generate a new
+     * instance for this class for each subscribe call to make it easier to differentiate between
+     * the callers.
+     */
+    private class BrowserSubscriptionCallback extends MediaBrowser.SubscriptionCallback {
+        BrowseCallback mCallback = null;
+
+        BrowserSubscriptionCallback(BrowseCallback cb) {
+            mCallback = cb;
+        }
+
+        @Override
+        public void onChildrenLoaded(String parentId, List<MediaItem> children) {
+            if (DEBUG) {
+                Log.d(TAG, "onChildrenLoaded: mediaId=" + parentId + " size= " + children.size());
+            }
+
+            if (mCallback == null) {
+                Log.w(TAG, "onChildrenLoaded: " + mPackageName
+                        + " children loaded while callback is null");
+            }
+
+            // TODO (apanicke): Instead of always unsubscribing, only unsubscribe from folders
+            // that aren't cached. This will let us update what is cached on the fly and prevent
+            // us from serving stale data.
+            mWrappedBrowser.unsubscribe(parentId);
+
+            ArrayList<ListItem> return_list = new ArrayList<ListItem>();
+
+            for (MediaItem item : children) {
+                if (DEBUG) {
+                    Log.d(TAG, "onChildrenLoaded: Child=\"" + item.toString()
+                            + "\",  ID=\"" + item.getMediaId() + "\"");
+                }
+
+                if (item.isBrowsable()) {
+                    CharSequence titleCharSequence = item.getDescription().getTitle();
+                    String title = "Not Provided";
+                    if (titleCharSequence != null) {
+                        title = titleCharSequence.toString();
+                    }
+                    Folder f = new Folder(item.getMediaId(), false, title);
+                    return_list.add(new ListItem(f));
+                } else {
+                    return_list.add(new ListItem(Util.toMetadata(item)));
+                }
+            }
+
+            mCachedFolders.put(parentId, return_list);
+
+            // Clone the list so that the callee can mutate it without affecting the cached data
+            mCallback.run(STATUS_SUCCESS, parentId, Util.cloneList(return_list));
+            mCallback = null;
+            disconnect();
+        }
+
+        /* mediaId is invalid */
+        @Override
+        public void onError(String id) {
+            Log.e(TAG, "BrowserSubscriptionCallback: Could not get folder items");
+            mCallback.run(STATUS_LOOKUP_ERROR, id, new ArrayList<ListItem>());
+            disconnect();
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Browsable Package Name: " + mPackageName + "\n");
+        sb.append("   Cached Media ID's: ");
+        for (String id : mCachedFolders.keySet()) {
+            sb.append("\"" + id + "\", ");
+        }
+        sb.append("\n");
+        return sb.toString();
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/GPMWrapper.java b/src/com/android/bluetooth/newavrcp/GPMWrapper.java
new file mode 100644
index 0000000..a7f41fb
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/GPMWrapper.java
@@ -0,0 +1,61 @@
+/*
+ * 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.avrcp;
+
+import android.media.session.MediaSession;
+import android.util.Log;
+
+/**
+ * Google Play Music hides some of the metadata behind a specific key in the Extras of the
+ * MediaDescription in the MediaSession.QueueItem. This class exists to provide alternate
+ * 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 boolean DEBUG = true;
+
+    @Override
+    boolean isMetadataSynced() {
+        if (getQueue() == null) {
+            return false;
+        }
+
+        // Check if currentPlayingQueueId is in the queue
+        MediaSession.QueueItem currItem = null;
+        for (MediaSession.QueueItem item : getQueue()) {
+            // The item exists in the current queue
+            if (item.getQueueId() == getActiveQueueID()) {
+                currItem = item;
+                break;
+            }
+        }
+
+        // Check if current playing song in Queue matches current Metadata
+        Metadata qitem = Util.toMetadata(currItem);
+        Metadata mdata = Util.toMetadata(getMetadata());
+        if (currItem == null || !qitem.equals(mdata)) {
+            if (DEBUG) {
+                Log.d(TAG, "Metadata currently out of sync for Google Play Music");
+                Log.d(TAG, "  └ Current queueItem: " + qitem);
+                Log.d(TAG, "  └ Current metadata : " + mdata);
+            }
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/MediaPlayerList.java b/src/com/android/bluetooth/newavrcp/MediaPlayerList.java
new file mode 100644
index 0000000..74ea039
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/MediaPlayerList.java
@@ -0,0 +1,688 @@
+/*
+ * 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.avrcp;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import com.android.bluetooth.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is directly responsible of maintaining the list of Browsable Players as well as
+ * the list of Addressable Players. This variation of the list doesn't actually list all the
+ * available players for a getAvailableMediaPlayers request. Instead it only reports one media
+ * player with ID=0 and all the other browsable players are folders in the root of that player.
+ *
+ * Changing the directory to a browsable player will allow you to traverse that player as normal.
+ * By only having one root player, we never have to send Addressed Player Changed notifications,
+ * UIDs Changed notifications, or Available Players Changed notifications.
+ *
+ * TODO (apanicke): Add non-browsable players as song items to the root folder. Selecting that
+ * 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 boolean DEBUG = true;
+    static boolean sTesting = false;
+
+    private static final String PACKAGE_SCHEME = "package";
+    private static final int NO_ACTIVE_PLAYER = 0;
+    private static final int BLUETOOTH_PLAYER_ID = 0;
+    private static final String BLUETOOTH_PLAYER_NAME = "Bluetooth Player";
+
+    // mediaId's for the now playing list will be in the form of "NowPlayingId[XX]" where [XX]
+    // is the Queue ID for the requested item.
+    private static final String NOW_PLAYING_ID_PATTERN = Util.NOW_PLAYING_PREFIX + "([0-9]*)";
+
+    // mediaId's for folder browsing will be in the form of [XX][mediaid],  where [XX] is a
+    // two digit representation of the player id and [mediaid] is the original media id as a
+    // string.
+    private static final String BROWSE_ID_PATTERN = "\\d\\d.*";
+
+    private Context mContext;
+    private Looper mLooper; // Thread all media player callbacks and timeouts happen on
+    private PackageManager mPackageManager;
+    private MediaSessionManager mMediaSessionManager;
+
+    private Map<Integer, MediaPlayerWrapper> mMediaPlayers =
+            Collections.synchronizedMap(new HashMap<Integer, MediaPlayerWrapper>());
+    private Map<String, Integer> mMediaPlayerIds =
+            Collections.synchronizedMap(new HashMap<String, Integer>());
+    private Map<Integer, BrowsedPlayerWrapper> mBrowsablePlayers =
+            Collections.synchronizedMap(new HashMap<Integer, BrowsedPlayerWrapper>());
+    private int mActivePlayerId = NO_ACTIVE_PLAYER;
+
+    private AvrcpTargetService.ListCallback mCallback;
+
+    interface MediaUpdateCallback {
+        void run(MediaData data);
+    }
+
+    interface GetPlayerRootCallback {
+        void run(int playerId, boolean success, String rootId, int numItems);
+    }
+
+    interface GetFolderItemsCallback {
+        void run(String parentId, List<ListItem> items);
+    }
+
+    interface FolderUpdateCallback {
+        void run(boolean availablePlayers, boolean addressedPlayers, boolean uids);
+    }
+
+    MediaPlayerList(Looper looper, Context context) {
+        Log.v(TAG, "Creating MediaPlayerList");
+
+        mLooper = looper;
+        mContext = context;
+
+        // Register for intents where available players might have changed
+        IntentFilter pkgFilter = new IntentFilter();
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        pkgFilter.addDataScheme(PACKAGE_SCHEME);
+        context.registerReceiver(mPackageChangedBroadcastReceiver, pkgFilter);
+
+        mMediaSessionManager =
+                (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+        mMediaSessionManager.addOnActiveSessionsChangedListener(
+                mActiveSessionsChangedListener, null, new Handler(looper));
+        mMediaSessionManager.setCallback(mButtonDispatchCallback, null);
+    }
+
+    void init(AvrcpTargetService.ListCallback callback) {
+        Log.v(TAG, "Initializing MediaPlayerList");
+        mCallback = callback;
+
+        // Build the list of browsable players and afterwards, build the list of media players
+        Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE);
+        List<ResolveInfo> playerList =
+                mContext
+                    .getApplicationContext()
+                    .getPackageManager()
+                    .queryIntentServices(intent, PackageManager.MATCH_ALL);
+
+        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
+                if (mMediaSessionManager == null) {
+                    return;
+                }
+
+                for (BrowsedPlayerWrapper wrapper : players) {
+                    // Generate new id and add the browsable player
+                    if (!mMediaPlayerIds.containsKey(wrapper.getPackageName())) {
+                        mMediaPlayerIds.put(wrapper.getPackageName(), getFreeMediaPlayerId());
+                    }
+
+                    d("Adding Browser Wrapper for " + wrapper.getPackageName() + " with id "
+                            + mMediaPlayerIds.get(wrapper.getPackageName()));
+
+                    mBrowsablePlayers.put(mMediaPlayerIds.get(wrapper.getPackageName()), wrapper);
+
+                    wrapper.getFolderItems(wrapper.getRootId(),
+                            (int status, String mediaId, List<ListItem> results) -> {
+                                d("Got the contents for: " + mediaId + " : num results="
+                                        + results.size());
+                            });
+                }
+
+                // Construct the list of current players
+                d("Initializing list of current media players");
+                List<android.media.session.MediaController> controllers =
+                        mMediaSessionManager.getActiveSessions(null);
+
+                for (android.media.session.MediaController controller : controllers) {
+                    addMediaPlayer(controller);
+                }
+
+                // If there were any active players and we don't already have one due to the Media
+                // Framework Callbacks then set the highest priority one to active
+                if (mActivePlayerId == 0 && mMediaPlayers.size() > 0) setActivePlayer(1);
+            });
+    }
+
+    void cleanup() {
+        mContext.unregisterReceiver(mPackageChangedBroadcastReceiver);
+
+        mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveSessionsChangedListener);
+        mMediaSessionManager.setCallback(null, null);
+        mMediaSessionManager = null;
+
+        mMediaPlayerIds.clear();
+
+        for (MediaPlayerWrapper player : mMediaPlayers.values()) {
+            player.cleanup();
+        }
+        mMediaPlayers.clear();
+
+        for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
+            player.disconnect();
+        }
+        mBrowsablePlayers.clear();
+    }
+
+    int getCurrentPlayerId() {
+        return BLUETOOTH_PLAYER_ID;
+    }
+
+    int getFreeMediaPlayerId() {
+        int id = 0;
+        while (mMediaPlayerIds.containsValue(++id)) {}
+        return id;
+    }
+
+    MediaPlayerWrapper getActivePlayer() {
+        return mMediaPlayers.get(mActivePlayerId);
+    }
+
+
+
+    // In this case the displayed player is the Bluetooth Player, the number of items is equal
+    // to the number of players. The root ID will always be empty string in this case as well.
+    void getPlayerRoot(int playerId, GetPlayerRootCallback cb) {
+        cb.run(playerId, playerId == BLUETOOTH_PLAYER_ID, "", mBrowsablePlayers.size());
+    }
+
+    // Return the "Bluetooth Player" as the only player always
+    List<PlayerInfo> getMediaPlayerList() {
+        PlayerInfo info = new PlayerInfo();
+        info.id = BLUETOOTH_PLAYER_ID;
+        info.name = BLUETOOTH_PLAYER_NAME;
+        info.browsable = true;
+        List<PlayerInfo> ret = new ArrayList<PlayerInfo>();
+        ret.add(info);
+
+        return ret;
+    }
+
+    @NonNull
+    String getCurrentMediaId() {
+        final MediaPlayerWrapper player = getActivePlayer();
+        if (player == null) return "";
+
+        final PlaybackState state = player.getPlaybackState();
+        final List<Metadata> queue = player.getCurrentQueue();
+
+        // Disable the now playing list if the player doesn't have a queue or provide an active
+        // queue ID that can be used to determine the active song in the queue.
+        if (state == null
+                || state.getActiveQueueItemId() == MediaSession.QueueItem.UNKNOWN_ID
+                || queue.size() == 0) {
+            d("getCurrentMediaId: No active queue item Id sending empty mediaId: PlaybackState="
+                     + state);
+            return "";
+        }
+
+        return Util.NOW_PLAYING_PREFIX + state.getActiveQueueItemId();
+    }
+
+    @NonNull
+    Metadata getCurrentSongInfo() {
+        final MediaPlayerWrapper player = getActivePlayer();
+        if (player == null) return Util.empty_data();
+
+        return player.getCurrentMetadata();
+    }
+
+    PlaybackState getCurrentPlayStatus() {
+        final MediaPlayerWrapper player = getActivePlayer();
+        if (player == null) return null;
+
+        return player.getPlaybackState();
+    }
+
+    @NonNull
+    List<Metadata> getNowPlayingList() {
+        // Only send the current song for the now playing if there is no active song. See
+        // |getCurrentMediaId()| for reasons why there might be no active song.
+        if (getCurrentMediaId().equals("")) {
+            List<Metadata> ret = new ArrayList<Metadata>();
+            Metadata data = getCurrentSongInfo();
+            data.mediaId = "";
+            ret.add(data);
+            return ret;
+        }
+
+        return getActivePlayer().getCurrentQueue();
+    }
+
+    void playItem(int playerId, boolean nowPlaying, String mediaId) {
+        if (nowPlaying) {
+            playNowPlayingItem(mediaId);
+        } else {
+            playFolderItem(mediaId);
+        }
+    }
+
+    private void playNowPlayingItem(String mediaId) {
+        d("playNowPlayingItem: mediaId=" + mediaId);
+
+        Pattern regex = Pattern.compile(NOW_PLAYING_ID_PATTERN);
+        Matcher m = regex.matcher(mediaId);
+        if (!m.find()) {
+            // This should never happen since we control the media ID's reported
+            Log.wtf(TAG, "playNowPlayingItem: Couldn't match mediaId to pattern: mediaId="
+                    + mediaId);
+        }
+
+        long queueItemId = Long.parseLong(m.group(1));
+        if (getActivePlayer() != null) {
+            getActivePlayer().playItemFromQueue(queueItemId);
+        }
+    }
+
+    private void playFolderItem(String mediaId) {
+        d("playFolderItem: mediaId=" + mediaId);
+
+        if (!mediaId.matches(BROWSE_ID_PATTERN)) {
+            // This should never happen since we control the media ID's reported
+            Log.wtf(TAG, "playFolderItem: mediaId didn't match pattern: mediaId=" + mediaId);
+        }
+
+        int playerIndex = Integer.parseInt(mediaId.substring(0, 2));
+        String itemId = mediaId.substring(2);
+
+        if (!mBrowsablePlayers.containsKey(playerIndex)) {
+            e("playFolderItem: Do not have the a browsable player with ID " + playerIndex);
+            return;
+        }
+
+        mBrowsablePlayers.get(playerIndex).playItem(itemId);
+    }
+
+    void getFolderItemsMediaPlayerList(GetFolderItemsCallback cb) {
+        d("getFolderItemsMediaPlayerList: Sending Media Player list for root directory");
+
+        ArrayList<ListItem> playerList = new ArrayList<ListItem>();
+        for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
+
+            String displayName = Util.getDisplayName(mContext, player.getPackageName());
+            int id = mMediaPlayerIds.get(player.getPackageName());
+
+            d("getFolderItemsMediaPlayerList: Adding player " + displayName);
+            Folder playerFolder = new Folder(String.format("%02d", id), false, displayName);
+            playerList.add(new ListItem(playerFolder));
+        }
+        cb.run("", playerList);
+        return;
+    }
+
+    void getFolderItems(int playerId, String mediaId, GetFolderItemsCallback cb) {
+        // The playerId is unused since we always assume the remote device is using the
+        // Bluetooth Player.
+        d("getFolderItems(): playerId=" + playerId + ", mediaId=" + mediaId);
+
+        // The device is requesting the content of the root folder. This folder contains a list of
+        // Browsable Media Players displayed as folders with their contents contained within.
+        if (mediaId.equals("")) {
+            getFolderItemsMediaPlayerList(cb);
+            return;
+        }
+
+        if (!mediaId.matches(BROWSE_ID_PATTERN)) {
+            // This should never happen since we control the media ID's reported
+            Log.wtf(TAG, "getFolderItems: mediaId didn't match pattern: mediaId=" + mediaId);
+        }
+
+        int playerIndex = Integer.parseInt(mediaId.substring(0, 2));
+        String itemId = mediaId.substring(2);
+
+        // TODO (apanicke): Add timeouts for looking up folder items since media browsers don't
+        // have to respond.
+        if (mBrowsablePlayers.containsKey(playerIndex)) {
+            BrowsedPlayerWrapper wrapper = mBrowsablePlayers.get(playerIndex);
+            if (itemId.equals("")) {
+                Log.i(TAG, "Empty media id, getting the root for "
+                        + wrapper.getPackageName());
+                itemId = wrapper.getRootId();
+            }
+
+            wrapper.getFolderItems(itemId, (status, id, results) -> {
+                if (status != BrowsedPlayerWrapper.STATUS_SUCCESS) {
+                    cb.run(mediaId, new ArrayList<ListItem>());
+                    return;
+                }
+
+                String playerPrefix = String.format("%02d", playerIndex);
+                for (ListItem item : results) {
+                    if (item.isFolder) {
+                        item.folder.mediaId = playerPrefix.concat(item.folder.mediaId);
+                    } else {
+                        item.song.mediaId = playerPrefix.concat(item.song.mediaId);
+                    }
+                }
+                cb.run(mediaId, results);
+            });
+            return;
+        } else {
+            cb.run(mediaId, new ArrayList<ListItem>());
+        }
+    }
+
+    // Adds the controller to the MediaPlayerList or updates the controller if we already had
+    // a controller for a package. Returns the new ID of the controller where its added or its
+    // previous value if it already existed. Returns -1 if the controller passed in is invalid
+    int addMediaPlayer(android.media.session.MediaController controller) {
+        if (controller == null) return -1;
+
+        // Each new player has an ID of 1 plus the highest ID. The ID 0 is reserved to signify that
+        // there is no active player. If we already have a browsable player for the package, reuse
+        // that key.
+        String packageName = controller.getPackageName();
+        if (!mMediaPlayerIds.containsKey(packageName)) {
+            mMediaPlayerIds.put(packageName, getFreeMediaPlayerId());
+        }
+
+        int playerId = mMediaPlayerIds.get(packageName);
+
+        // If we already have a controller for the package, then update it with this new controller
+        // as the old controller has probably gone stale.
+        if (mMediaPlayers.containsKey(playerId)) {
+            d("Already have a controller for the player: " + packageName + ", updating instead");
+            MediaPlayerWrapper player = mMediaPlayers.get(playerId);
+            player.updateMediaController(MediaControllerFactory.wrap(controller));
+
+            // If the media controller we updated was the active player check if the media updated
+            if (playerId == mActivePlayerId) {
+                sendMediaUpdate(getActivePlayer().getCurrentMediaData());
+            }
+
+            return playerId;
+        }
+
+        MediaPlayerWrapper newPlayer = MediaPlayerWrapper.wrap(
+                MediaControllerFactory.wrap(controller),
+                mLooper);
+
+        Log.i(TAG, "Adding wrapped media player: " + packageName + " at key: "
+                + mMediaPlayerIds.get(controller.getPackageName()));
+
+        mMediaPlayers.put(playerId, newPlayer);
+        return playerId;
+    }
+
+    void removeMediaPlayer(int playerId) {
+        if (!mMediaPlayers.containsKey(playerId)) {
+            e("Trying to remove nonexistent media player: " + playerId);
+            return;
+        }
+
+        // If we removed the active player, set no player as active until the Media Framework
+        // tells us otherwise
+        if (playerId == mActivePlayerId && playerId != NO_ACTIVE_PLAYER) {
+            getActivePlayer().unregisterCallback();
+            mActivePlayerId = NO_ACTIVE_PLAYER;
+        }
+
+        final MediaPlayerWrapper wrapper = mMediaPlayers.get(playerId);
+        d("Removing media player " + wrapper.getPackageName());
+        mMediaPlayerIds.remove(wrapper.getPackageName());
+        mMediaPlayers.remove(playerId);
+        wrapper.cleanup();
+    }
+
+    void setActivePlayer(int playerId) {
+        if (!mMediaPlayers.containsKey(playerId)) {
+            e("Player doesn't exist in list(): " + playerId);
+            return;
+        }
+
+        if (playerId == mActivePlayerId) {
+            Log.w(TAG, getActivePlayer().getPackageName() + " is already the active player");
+            return;
+        }
+
+        if (mActivePlayerId != NO_ACTIVE_PLAYER) getActivePlayer().unregisterCallback();
+
+        mActivePlayerId = playerId;
+        getActivePlayer().registerCallback(mMediaPlayerCallback);
+        Log.i(TAG, "setActivePlayer(): setting player to " + getActivePlayer().getPackageName());
+
+        // Ensure that metadata is synced on the new player
+        if (!getActivePlayer().isMetadataSynced()) {
+            Log.w(TAG, "setActivePlayer(): Metadata not synced on new player");
+            return;
+        }
+
+        if (Utils.isPtsTestMode()) {
+            sendFolderUpdate(true, true, false);
+        }
+
+        sendMediaUpdate(getActivePlayer().getCurrentMediaData());
+    }
+
+    // TODO (apanicke): Add logging for media key events in dumpsys
+    void sendMediaKeyEvent(int key, boolean pushed) {
+        d("sendMediaKeyEvent: key=" + key + " pushed=" + pushed);
+        int action = pushed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
+        KeyEvent event = new KeyEvent(action, AvrcpPassthrough.toKeyCode(key));
+        mMediaSessionManager.dispatchMediaKeyEvent(event);
+    }
+
+    private void sendFolderUpdate(boolean availablePlayers, boolean addressedPlayers,
+            boolean uids) {
+        d("sendFolderUpdate");
+        if (mCallback == null) {
+            return;
+        }
+
+        mCallback.run(availablePlayers, addressedPlayers, uids);
+    }
+
+    private void sendMediaUpdate(MediaData data) {
+        d("sendMediaUpdate");
+        if (mCallback == null) {
+            return;
+        }
+
+        // Always have items in the queue
+        if (data.queue.size() == 0) {
+            Log.i(TAG, "sendMediaUpdate: Creating a one item queue for a player with no queue");
+            data.queue.add(data.metadata);
+        }
+
+        mCallback.run(data);
+    }
+
+    private final MediaSessionManager.OnActiveSessionsChangedListener
+            mActiveSessionsChangedListener =
+            new MediaSessionManager.OnActiveSessionsChangedListener() {
+        @Override
+        public void onActiveSessionsChanged(
+                List<android.media.session.MediaController> newControllers) {
+            synchronized (MediaPlayerList.this) {
+                Log.v(TAG, "onActiveSessionsChanged: number of controllers: "
+                        + newControllers.size());
+                if (newControllers.size() == 0) return;
+
+                // Apps are allowed to have multiple MediaControllers. If an app does have
+                // multiple controllers then newControllers contains them in highest
+                // priority order. Since we only want to keep the highest priority one,
+                // we keep track of which controllers we updated and skip over ones
+                // we've already looked at.
+                HashSet<String> addedPackages = new HashSet<String>();
+
+                for (int i = 0; i < newControllers.size(); i++) {
+                    Log.d(TAG, "onActiveSessionsChanged: controller: "
+                            + newControllers.get(i).getPackageName());
+                    if (addedPackages.contains(newControllers.get(i).getPackageName())) {
+                        continue;
+                    }
+
+                    addedPackages.add(newControllers.get(i).getPackageName());
+                    addMediaPlayer(newControllers.get(i));
+                }
+            }
+        }
+    };
+
+    // TODO (apanicke): Write a test that tests uninstalling the active session
+    private final BroadcastReceiver mPackageChangedBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.v(TAG, "mPackageChangedBroadcastReceiver: action: " + action);
+
+            if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
+                    || action.equals(Intent.ACTION_PACKAGE_DATA_CLEARED)) {
+                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) return;
+
+                String packageName = intent.getData().getSchemeSpecificPart();
+                if (packageName != null && mMediaPlayerIds.containsKey(packageName)) {
+                    removeMediaPlayer(mMediaPlayerIds.get(packageName));
+                }
+            } else if (action.equals(Intent.ACTION_PACKAGE_ADDED)
+                    || action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
+                String packageName = intent.getData().getSchemeSpecificPart();
+                if (packageName != null) {
+                    if (DEBUG) Log.d(TAG, "Name of package changed: " + packageName);
+                    // TODO (apanicke): Handle either updating or adding the new package.
+                    // Check if its browsable and send the UIDS changed to update the
+                    // root folder
+                }
+            }
+        }
+    };
+
+    private final MediaPlayerWrapper.Callback mMediaPlayerCallback =
+            new MediaPlayerWrapper.Callback() {
+        @Override
+        public void mediaUpdatedCallback(MediaData data) {
+            if (data.metadata == null) {
+                Log.d(TAG, "mediaUpdatedCallback(): metadata is null");
+                return;
+            }
+
+            if (data.state == null) {
+                Log.w(TAG, "mediaUpdatedCallback(): Tried to update with null state");
+                return;
+            }
+
+            sendMediaUpdate(data);
+        }
+    };
+
+    private final MediaSessionManager.Callback mButtonDispatchCallback =
+            new MediaSessionManager.Callback() {
+                @Override
+                public void onMediaKeyEventDispatched(KeyEvent event, MediaSession.Token token) {
+                    // TODO (apanicke): Add logging for these
+                }
+
+                @Override
+                public void onMediaKeyEventDispatched(KeyEvent event, ComponentName receiver) {
+                    // TODO (apanicke): Add logging for these
+                }
+
+                @Override
+                public void onAddressedPlayerChanged(MediaSession.Token token) {
+                    android.media.session.MediaController controller =
+                            new android.media.session.MediaController(mContext, token);
+
+                    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.
+                        Log.w(TAG, "onAddressedPlayerChanged(Token): Addressed Player "
+                                + "changed to a player we didn't have a session for");
+                        addMediaPlayer(controller);
+                    }
+
+                    Log.i(TAG, "onAddressedPlayerChanged: token=" + controller.getPackageName());
+                    setActivePlayer(mMediaPlayerIds.get(controller.getPackageName()));
+                }
+
+                @Override
+                public void onAddressedPlayerChanged(ComponentName receiver) {
+                    if (receiver == null) {
+                        return;
+                    }
+
+                    if (!mMediaPlayerIds.containsKey(receiver.getPackageName())) {
+                        e("onAddressedPlayerChanged(Component): Addressed Player "
+                                + "changed to a player we don't have a session for");
+                        return;
+                    }
+
+                    Log.i(TAG, "onAddressedPlayerChanged: component=" + receiver.getPackageName());
+                    setActivePlayer(mMediaPlayerIds.get(receiver.getPackageName()));
+                }
+            };
+
+
+    void dump(StringBuilder sb) {
+        sb.append("List of MediaControllers: size=" + mMediaPlayers.size() + "\n");
+        for (int id : mMediaPlayers.keySet()) {
+            if (id == mActivePlayerId) {
+                sb.append("<Active> ");
+            }
+            MediaPlayerWrapper player = mMediaPlayers.get(id);
+            sb.append("  Media Player " + id + ": " + player.getPackageName() + "\n");
+            sb.append(player.toString().replaceAll("(?m)^", "  "));
+            sb.append("\n");
+        }
+
+        sb.append("List of Browsers: size=" + mBrowsablePlayers.size() + "\n");
+        for (BrowsedPlayerWrapper player : mBrowsablePlayers.values()) {
+            sb.append(player.toString().replaceAll("(?m)^", "  "));
+            sb.append("\n");
+        }
+        // TODO (apanicke): Add media key events
+        // TODO (apanicke): Add last sent data
+        // TODO (apanicke): Add addressed player history
+    }
+
+    private static void e(String message) {
+        if (sTesting) {
+            Log.wtfStack(TAG, message);
+        } else {
+            Log.e(TAG, message);
+        }
+    }
+
+    private static void d(String message) {
+        if (DEBUG) {
+            Log.d(TAG, message);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java b/src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java
new file mode 100644
index 0000000..256e771
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/MediaPlayerWrapper.java
@@ -0,0 +1,526 @@
+/*
+ * 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.avrcp;
+
+import android.annotation.Nullable;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+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 java.util.List;
+import java.util.Objects;
+
+/*
+ * A class to synchronize Media Controller Callbacks and only pass through
+ * an update once all the relevant information is current.
+ *
+ * TODO (apanicke): Once MediaPlayer2 is supported better, replace this class
+ * with that.
+ */
+class MediaPlayerWrapper {
+    private static final String TAG = "NewAvrcpMediaPlayerWrapper";
+    private static final boolean DEBUG = true;
+    static boolean sTesting = false;
+
+    private MediaController mMediaController;
+    private String mPackageName;
+    private Looper mLooper;
+
+    private MediaData mCurrentData;
+
+    @GuardedBy("mCallbackLock")
+    private MediaControllerListener mControllerCallbacks = null;
+    private final Object mCallbackLock = new Object();
+    private Callback mRegisteredCallback = null;
+
+    protected MediaPlayerWrapper() {
+        mCurrentData = new MediaData(null, null, null);
+    }
+
+    public interface Callback {
+        void mediaUpdatedCallback(MediaData data);
+    }
+
+    boolean isReady() {
+        if (getPlaybackState() == null) {
+            d("isReady(): PlaybackState is null");
+            return false;
+        }
+
+        if (getMetadata() == null) {
+            d("isReady(): Metadata is null");
+            return false;
+        }
+
+        return true;
+    }
+
+    // TODO (apanicke): Implement a factory to make testing and creating interop wrappers easier
+    static MediaPlayerWrapper wrap(MediaController controller, Looper looper) {
+        if (controller == null || looper == null) {
+            e("MediaPlayerWrapper.wrap(): Null parameter - Controller: " + controller
+                    + " | Looper: " + looper);
+            return null;
+        }
+
+        MediaPlayerWrapper newWrapper;
+        if (controller.getPackageName().equals("com.google.android.music")) {
+            Log.v(TAG, "Creating compatibility wrapper for Google Play Music");
+            newWrapper = new GPMWrapper();
+        } else {
+            newWrapper = new MediaPlayerWrapper();
+        }
+
+        newWrapper.mMediaController = controller;
+        newWrapper.mPackageName = controller.getPackageName();
+        newWrapper.mLooper = looper;
+
+        newWrapper.mCurrentData.queue = Util.toMetadataList(newWrapper.getQueue());
+        newWrapper.mCurrentData.metadata = Util.toMetadata(newWrapper.getMetadata());
+        newWrapper.mCurrentData.state = newWrapper.getPlaybackState();
+        return newWrapper;
+    }
+
+    void cleanup() {
+        unregisterCallback();
+
+        mMediaController = null;
+        mLooper = null;
+    }
+
+    String getPackageName() {
+        return mPackageName;
+    }
+
+    protected List<MediaSession.QueueItem> getQueue() {
+        return mMediaController.getQueue();
+    }
+
+    protected MediaMetadata getMetadata() {
+        return mMediaController.getMetadata();
+    }
+
+    Metadata getCurrentMetadata() {
+        return Util.toMetadata(getMetadata());
+    }
+
+    PlaybackState getPlaybackState() {
+        return mMediaController.getPlaybackState();
+    }
+
+    long getActiveQueueID() {
+        if (mMediaController.getPlaybackState() == null) return -1;
+        return mMediaController.getPlaybackState().getActiveQueueItemId();
+    }
+
+    List<Metadata> getCurrentQueue() {
+        return mCurrentData.queue;
+    }
+
+    // We don't return the cached info here in order to always provide the freshest data.
+    MediaData getCurrentMediaData() {
+        MediaData data = new MediaData(
+                getCurrentMetadata(),
+                getPlaybackState(),
+                getCurrentQueue());
+        return data;
+    }
+
+    void playItemFromQueue(long qid) {
+        // Return immediately if no queue exists.
+        if (getQueue() == null) {
+            Log.w(TAG, "playItemFromQueue: Trying to play item for player that has no queue: "
+                    + mPackageName);
+            return;
+        }
+
+        MediaController.TransportControls controller = mMediaController.getTransportControls();
+        controller.skipToQueueItem(qid);
+    }
+
+    // TODO (apanicke): Implement shuffle and repeat support. Right now these use custom actions
+    // and it may only be possible to do this with Google Play Music
+    boolean isShuffleSupported() {
+        return false;
+    }
+
+    boolean isRepeatSupported() {
+        return false;
+    }
+
+    void toggleShuffle(boolean on) {
+        return;
+    }
+
+    void toggleRepeat(boolean on) {
+        return;
+    }
+
+    /**
+     * Return whether the queue, metadata, and queueID are all in sync.
+     */
+    boolean isMetadataSynced() {
+        if (getQueue() != null && getActiveQueueID() != -1) {
+            // Check if currentPlayingQueueId is in the current Queue
+            MediaSession.QueueItem currItem = null;
+
+            for (MediaSession.QueueItem item : getQueue()) {
+                if (item.getQueueId()
+                        == getActiveQueueID()) { // The item exists in the current queue
+                    currItem = item;
+                    break;
+                }
+            }
+
+            // Check if current playing song in Queue matches current Metadata
+            Metadata qitem = Util.toMetadata(currItem);
+            Metadata mdata = Util.toMetadata(getMetadata());
+            if (currItem == null || !qitem.equals(mdata)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Metadata currently out of sync for " + mPackageName);
+                    Log.d(TAG, "  └ Current queueItem: " + qitem);
+                    Log.d(TAG, "  └ Current metadata : " + mdata);
+                }
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Register a callback which gets called when media updates happen. The callbacks are
+     * called on the same Looper that was passed in to create this object.
+     */
+    void registerCallback(Callback callback) {
+        if (callback == null) {
+            e("Cannot register null callbacks for " + mPackageName);
+            return;
+        }
+
+        synchronized (mCallbackLock) {
+            mRegisteredCallback = callback;
+        }
+
+        // Update the current data since it could have changed while we weren't registered for
+        // updates
+        mCurrentData = new MediaData(
+                Util.toMetadata(getMetadata()),
+                getPlaybackState(),
+                Util.toMetadataList(getQueue()));
+
+        mControllerCallbacks = new MediaControllerListener(mMediaController, mLooper);
+    }
+
+    /**
+     * Unregisters from updates. Note, this doesn't require the looper to be shut down.
+     */
+    void unregisterCallback() {
+        // Prevent a race condition where a callback could be called while shutting down
+        synchronized (mCallbackLock) {
+            mRegisteredCallback = null;
+        }
+
+        if (mControllerCallbacks == null) return;
+        mControllerCallbacks.cleanup();
+        mControllerCallbacks = null;
+    }
+
+    void updateMediaController(MediaController newController) {
+        if (newController == mMediaController) return;
+
+        mMediaController = newController;
+
+        synchronized (mCallbackLock) {
+            if (mRegisteredCallback == null || mControllerCallbacks == null) {
+                d("Controller for " + mPackageName + " maybe is not activated.");
+                return;
+            }
+        }
+
+        mControllerCallbacks.cleanup();
+
+        // Update the current data since it could be different on the new controller for the player
+        mCurrentData = new MediaData(
+                Util.toMetadata(getMetadata()),
+                getPlaybackState(),
+                Util.toMetadataList(getQueue()));
+
+        mControllerCallbacks = new MediaControllerListener(mMediaController, mLooper);
+        d("Controller for " + mPackageName + " was updated.");
+    }
+
+    private void sendMediaUpdate() {
+        MediaData newData = new MediaData(
+                Util.toMetadata(getMetadata()),
+                getPlaybackState(),
+                Util.toMetadataList(getQueue()));
+
+        if (newData.equals(mCurrentData)) {
+            // This may happen if the controller is fully synced by the time the
+            // first update is completed
+            Log.v(TAG, "Trying to update with last sent metadata");
+            return;
+        }
+
+        synchronized (mCallbackLock) {
+            if (mRegisteredCallback == null) {
+                Log.e(TAG, mPackageName
+                        + ": Trying to send an update with no registered callback");
+                return;
+            }
+
+            Log.v(TAG, "trySendMediaUpdate(): Metadata has been updated for " + mPackageName);
+            mRegisteredCallback.mediaUpdatedCallback(newData);
+        }
+
+        mCurrentData = newData;
+    }
+
+    class TimeoutHandler extends Handler {
+        private static final int MSG_TIMEOUT = 0;
+        private static final long CALLBACK_TIMEOUT_MS = 2000;
+
+        TimeoutHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what != MSG_TIMEOUT) {
+                Log.wtf(TAG, "Unknown message on timeout handler: " + msg.what);
+                return;
+            }
+
+            Log.e(TAG, "Timeout while waiting for metadata to sync for " + mPackageName);
+            Log.e(TAG, "  └ Current Metadata: " +  Util.toMetadata(getMetadata()));
+            Log.e(TAG, "  └ Current Playstate: " + getPlaybackState());
+            List<Metadata> current_queue = Util.toMetadataList(getQueue());
+            for (int i = 0; i < current_queue.size(); i++) {
+                Log.e(TAG, "  └ QueueItem(" + i + "): " + current_queue.get(i));
+            }
+
+            sendMediaUpdate();
+
+            // TODO(apanicke): Add metric collection here.
+
+            if (sTesting) Log.wtfStack(TAG, "Crashing the stack");
+        }
+    }
+
+    class MediaControllerListener extends MediaController.Callback {
+        private final Object mTimeoutHandlerLock = new Object();
+        private Handler mTimeoutHandler;
+        private MediaController mController;
+
+        MediaControllerListener(MediaController controller, Looper newLooper) {
+            synchronized (mTimeoutHandlerLock) {
+                mTimeoutHandler = new TimeoutHandler(newLooper);
+
+                mController = controller;
+                // Register the callbacks to execute on the same thread as the timeout thread. This
+                // prevents a race condition where a timeout happens at the same time as an update.
+                mController.registerCallback(this, mTimeoutHandler);
+            }
+        }
+
+        void cleanup() {
+            synchronized (mTimeoutHandlerLock) {
+                mController.unregisterCallback(this);
+                mController = null;
+                mTimeoutHandler.removeMessages(TimeoutHandler.MSG_TIMEOUT);
+                mTimeoutHandler = null;
+            }
+        }
+
+        void trySendMediaUpdate() {
+            synchronized (mTimeoutHandlerLock) {
+                if (mTimeoutHandler == null) return;
+                mTimeoutHandler.removeMessages(TimeoutHandler.MSG_TIMEOUT);
+
+                if (!isMetadataSynced()) {
+                    d("trySendMediaUpdate(): Starting media update timeout");
+                    mTimeoutHandler.sendEmptyMessageDelayed(TimeoutHandler.MSG_TIMEOUT,
+                            TimeoutHandler.CALLBACK_TIMEOUT_MS);
+                    return;
+                }
+            }
+
+            sendMediaUpdate();
+        }
+
+        @Override
+        public void onMetadataChanged(@Nullable MediaMetadata metadata) {
+            if (!isReady()) {
+                Log.v(TAG, "onMetadataChanged(): " + mPackageName
+                        + " tried to update with no queue");
+                return;
+            }
+
+            Log.v(TAG, "onMetadataChanged(): " + mPackageName + " : " + Util.toMetadata(metadata));
+
+            if (!Objects.equals(metadata, getMetadata())) {
+                e("The callback metadata doesn't match controller metadata");
+            }
+
+            // TODO: Certain players update different metadata fields as they load, such as Album
+            // Art. For track changed updates we only care about the song information like title
+            // and album and duration. In the future we can use this to know when Album art is
+            // loaded.
+
+            // TODO: Spotify needs a metadata update debouncer as it sometimes updates the metadata
+            // twice in a row with the only difference being that the song duration is rounded to
+            // the nearest second.
+            if (Objects.equals(metadata, mCurrentData.metadata)) {
+                Log.w(TAG, "onMetadataChanged(): " + mPackageName
+                        + " tried to update with no new data");
+                return;
+            }
+
+            trySendMediaUpdate();
+        }
+
+        @Override
+        public void onPlaybackStateChanged(@Nullable PlaybackState state) {
+            if (!isReady()) {
+                Log.v(TAG, "onPlaybackStateChanged(): " + mPackageName
+                        + " tried to update with no queue");
+                return;
+            }
+
+            Log.v(TAG, "onPlaybackStateChanged(): " + mPackageName + " : " + state.toString());
+
+            if (!playstateEquals(state, getPlaybackState())) {
+                e("The callback playback state doesn't match the current state");
+            }
+
+            if (playstateEquals(state, mCurrentData.state)) {
+                Log.w(TAG, "onPlaybackStateChanged(): " + mPackageName
+                        + " tried to update with no new data");
+                return;
+            }
+
+            // If there is no playstate, ignore the update.
+            if (state.getState() == PlaybackState.STATE_NONE) {
+                Log.v(TAG, "Waiting to send update as controller has no playback state");
+                return;
+            }
+
+            trySendMediaUpdate();
+        }
+
+        @Override
+        public void onQueueChanged(@Nullable List<MediaSession.QueueItem> queue) {
+            if (!isReady()) {
+                Log.v(TAG, "onQueueChanged(): " + mPackageName
+                        + " tried to update with no queue");
+                return;
+            }
+
+            Log.v(TAG, "onQueueChanged(): " + mPackageName);
+
+            if (!Objects.equals(queue, getQueue())) {
+                e("The callback queue isn't the current queue");
+            }
+
+            List<Metadata> current_queue = Util.toMetadataList(queue);
+            if (current_queue.equals(mCurrentData.queue)) {
+                Log.w(TAG, "onQueueChanged(): " + mPackageName
+                        + " tried to update with no new data");
+                return;
+            }
+
+            if (DEBUG) {
+                for (int i = 0; i < current_queue.size(); i++) {
+                    Log.d(TAG, "  └ QueueItem(" + i + "): " + current_queue.get(i));
+                }
+            }
+
+            trySendMediaUpdate();
+        }
+
+        @Override
+        public void onSessionDestroyed() {
+            Log.w(TAG, "The session was destroyed " + mPackageName);
+        }
+
+        @VisibleForTesting
+        Handler getTimeoutHandler() {
+            return mTimeoutHandler;
+        }
+    }
+
+    /**
+     * Checks wheter the core information of two PlaybackStates match. This function allows a
+     * certain amount of deviation between the position fields of the PlaybackStates. This is to
+     * prevent matches from failing when updates happen in quick succession.
+     *
+     * The maximum allowed deviation is defined by PLAYSTATE_BOUNCE_IGNORE_PERIOD and is measured
+     * in milliseconds.
+     */
+    private static final long PLAYSTATE_BOUNCE_IGNORE_PERIOD = 500;
+    static boolean playstateEquals(PlaybackState a, PlaybackState b) {
+        if (a == b) return true;
+
+        if (a != null && b != null
+                && a.getState() == b.getState()
+                && a.getActiveQueueItemId() == b.getActiveQueueItemId()
+                && Math.abs(a.getPosition() - b.getPosition()) < PLAYSTATE_BOUNCE_IGNORE_PERIOD) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private static void e(String message) {
+        if (sTesting) {
+            Log.wtfStack(TAG, message);
+        } else {
+            Log.e(TAG, message);
+        }
+    }
+
+    private void d(String message) {
+        if (DEBUG) Log.d(TAG, mPackageName + ": " + message);
+    }
+
+    @VisibleForTesting
+    Handler getTimeoutHandler() {
+        if (mControllerCallbacks == null) return null;
+        return mControllerCallbacks.getTimeoutHandler();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(mMediaController.toString() + "\n");
+        sb.append("Current Data:\n");
+        sb.append("  Song: " + mCurrentData.metadata + "\n");
+        sb.append("  PlayState: " + mCurrentData.state + "\n");
+        sb.append("  Queue: size=" + mCurrentData.queue.size() + "\n");
+        for (Metadata data : mCurrentData.queue) {
+            sb.append("    " + data + "\n");
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/helpers/AvrcpPassthrough.java b/src/com/android/bluetooth/newavrcp/helpers/AvrcpPassthrough.java
new file mode 100644
index 0000000..59ca419
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/helpers/AvrcpPassthrough.java
@@ -0,0 +1,134 @@
+/*
+ * 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.avrcp;
+
+import android.bluetooth.BluetoothAvrcp;
+import android.view.KeyEvent;
+
+class AvrcpPassthrough {
+    public static int toKeyCode(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;
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/helpers/Folder.java b/src/com/android/bluetooth/newavrcp/helpers/Folder.java
new file mode 100644
index 0000000..7786cf3
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/helpers/Folder.java
@@ -0,0 +1,34 @@
+/*
+ * 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.avrcp;
+
+class Folder implements Cloneable {
+    public String mediaId;
+    public boolean isPlayable;
+    public String title;
+
+    Folder(String i, boolean p, String t) {
+        mediaId = i;
+        isPlayable = p;
+        title = t;
+    }
+
+    @Override
+    public Folder clone() {
+        return new Folder(mediaId, isPlayable, title);
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/helpers/ListItem.java b/src/com/android/bluetooth/newavrcp/helpers/ListItem.java
new file mode 100644
index 0000000..210906b
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/helpers/ListItem.java
@@ -0,0 +1,42 @@
+/*
+ * 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.avrcp;
+
+class ListItem implements Cloneable {
+    public boolean isFolder = false;
+    public Folder folder;
+    public Metadata song;
+
+    ListItem(Folder f) {
+        isFolder = true;
+        folder = f;
+    }
+
+    ListItem(Metadata s) {
+        isFolder = false;
+        song = s;
+    }
+
+    @Override
+    public ListItem clone() {
+        if (isFolder) {
+            return new ListItem(folder.clone());
+        } else {
+            return new ListItem(song.clone());
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/helpers/MediaData.java b/src/com/android/bluetooth/newavrcp/helpers/MediaData.java
new file mode 100644
index 0000000..6646f90
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/helpers/MediaData.java
@@ -0,0 +1,59 @@
+/*
+ * 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.avrcp;
+
+import android.media.session.PlaybackState;
+
+import java.util.List;
+import java.util.Objects;
+
+/*
+ * Helper class to transport metadata around AVRCP
+ */
+class MediaData {
+    public List<Metadata> queue;
+    public PlaybackState state;
+    public Metadata metadata;
+
+    MediaData(Metadata m, PlaybackState s, List<Metadata> q) {
+        metadata = m;
+        state = s;
+        queue = q;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) return false;
+        if (!(o instanceof MediaData)) return false;
+
+        final MediaData u = (MediaData) o;
+
+        if (!MediaPlayerWrapper.playstateEquals(state, u.state)) {
+            return false;
+        }
+
+        if (!Objects.equals(metadata, u.metadata)) {
+            return false;
+        }
+
+        if (!Objects.equals(queue, u.queue)) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/helpers/Metadata.java b/src/com/android/bluetooth/newavrcp/helpers/Metadata.java
new file mode 100644
index 0000000..ce97ea0
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/helpers/Metadata.java
@@ -0,0 +1,64 @@
+/*
+ * 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.avrcp;
+
+import java.util.Objects;
+
+class Metadata implements Cloneable {
+    public String mediaId;
+    public String title;
+    public String artist;
+    public String album;
+    public String trackNum;
+    public String numTracks;
+    public String genre;
+    public String duration;
+
+    @Override
+    public Metadata clone() {
+        Metadata data = new Metadata();
+        data.mediaId = mediaId;
+        data.title = title;
+        data.artist = artist;
+        data.album = album;
+        data.trackNum = trackNum;
+        data.numTracks = numTracks;
+        data.genre = genre;
+        data.duration = duration;
+        return data;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) return false;
+        if (!(o instanceof Metadata)) return false;
+
+        final Metadata m = (Metadata) o;
+        if (!Objects.equals(title, m.title)) return false;
+        if (!Objects.equals(artist, m.artist)) return false;
+        if (!Objects.equals(album, m.album)) return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "{ mediaId=\"" + mediaId + "\" title=\"" + title + "\" artist=\"" + artist
+                + "\" album=\"" + album + "\" duration=" + duration
+                + " trackPosition=" + trackNum + "/" + numTracks + " }";
+
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/helpers/PlayStatus.java b/src/com/android/bluetooth/newavrcp/helpers/PlayStatus.java
new file mode 100644
index 0000000..2c69bbd
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/helpers/PlayStatus.java
@@ -0,0 +1,82 @@
+/*
+ * 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.avrcp;
+
+import android.media.session.PlaybackState;
+
+/**
+ * Carries the playback status information in a custom object.
+ */
+// TODO(apanicke): Send the current active song ID along with this object so that all information
+// is carried by our custom types.
+class PlayStatus {
+    static final byte STOPPED = 0;
+    static final byte PLAYING = 1;
+    static final byte PAUSED = 2;
+    static final byte FWD_SEEK = 3;
+    static final byte REV_SEEK = 4;
+    static final byte ERROR = -1;
+
+    public long position = 0xFFFFFFFFFFFFFFFFL;
+    public long duration = 0x00L;
+    public byte state = STOPPED;
+
+    // Duration info isn't contained in the PlaybackState so the service must supply it.
+    static PlayStatus fromPlaybackState(PlaybackState state, long duration) {
+        PlayStatus ret = new PlayStatus();
+        if (state == null) return ret;
+
+        ret.state = playbackStateToAvrcpState(state.getState());
+        ret.position = state.getPosition();
+        ret.duration = duration;
+        return ret;
+    }
+
+    static byte playbackStateToAvrcpState(int playbackState) {
+        switch (playbackState) {
+            case PlaybackState.STATE_STOPPED:
+            case PlaybackState.STATE_NONE:
+            case PlaybackState.STATE_CONNECTING:
+                return PlayStatus.STOPPED;
+
+            case PlaybackState.STATE_BUFFERING:
+            case PlaybackState.STATE_PLAYING:
+                return PlayStatus.PLAYING;
+
+            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;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "{ state=" + state + " position=" + position + " duration=" + duration + " }";
+    }
+}
diff --git a/src/com/android/bluetooth/newavrcp/helpers/PlayerInfo.java b/src/com/android/bluetooth/newavrcp/helpers/PlayerInfo.java
new file mode 100644
index 0000000..96b152b
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/helpers/PlayerInfo.java
@@ -0,0 +1,26 @@
+/*
+ * 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.avrcp;
+
+/**
+ * Carries Media Player Information
+ */
+class PlayerInfo {
+    public int id;
+    public String name;
+    public boolean browsable;
+}
diff --git a/src/com/android/bluetooth/newavrcp/helpers/Util.java b/src/com/android/bluetooth/newavrcp/helpers/Util.java
new file mode 100644
index 0000000..eb84b0a
--- /dev/null
+++ b/src/com/android/bluetooth/newavrcp/helpers/Util.java
@@ -0,0 +1,240 @@
+/*
+ * 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.avrcp;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class Util {
+    public static String TAG = "NewAvrcpUtil";
+    public static boolean DEBUG = false;
+
+    private static final String GPM_KEY = "com.google.android.music.mediasession.music_metadata";
+
+    // TODO (apanicke): Remove this prefix later, for now it makes debugging easier.
+    public static final String NOW_PLAYING_PREFIX = "NowPlayingId";
+
+    public static final Metadata empty_data() {
+        Metadata ret = new Metadata();
+        ret.mediaId = "Not Provided";
+        ret.title = "Not Provided";
+        ret.artist = "";
+        ret.album = "";
+        ret.genre = "";
+        ret.trackNum = "1";
+        ret.numTracks = "1";
+        ret.duration = "0";
+        return ret;
+    }
+
+    public static Metadata bundleToMetadata(Bundle bundle) {
+        if (bundle == null) return empty_data();
+
+        Metadata temp = new Metadata();
+        temp.title = bundle.getString(MediaMetadata.METADATA_KEY_TITLE, "Not Provided");
+        temp.artist = bundle.getString(MediaMetadata.METADATA_KEY_ARTIST, "");
+        temp.album = bundle.getString(MediaMetadata.METADATA_KEY_ALBUM, "");
+        temp.trackNum = "" + bundle.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, 1);
+        temp.numTracks = "" + bundle.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, 1);
+        temp.genre = bundle.getString(MediaMetadata.METADATA_KEY_GENRE, "");
+        temp.duration = "" + bundle.getLong(MediaMetadata.METADATA_KEY_DURATION, 0);
+        return temp;
+    }
+
+    public static Bundle descriptionToBundle(MediaDescription desc) {
+        Bundle ret = new Bundle();
+        if (desc == null) return ret;
+
+        if (desc.getTitle() != null) {
+            ret.putString(MediaMetadata.METADATA_KEY_TITLE, desc.getTitle().toString());
+        }
+
+        if (desc.getSubtitle() != null) {
+            ret.putString(MediaMetadata.METADATA_KEY_ARTIST, desc.getSubtitle().toString());
+        }
+
+        if (desc.getDescription() != null) {
+            ret.putString(MediaMetadata.METADATA_KEY_ALBUM, desc.getDescription().toString());
+        }
+
+        // If the bundle has title or artist use those over the description title or subtitle.
+        if (desc.getExtras() != null) ret.putAll(desc.getExtras());
+
+        if (ret.containsKey(GPM_KEY)) {
+            if (DEBUG) Log.d(TAG, "MediaDescription contains GPM data");
+            ret.putAll(mediaMetadataToBundle((MediaMetadata) ret.get(GPM_KEY)));
+        }
+
+        return ret;
+    }
+
+    public static Bundle mediaMetadataToBundle(MediaMetadata data) {
+        Bundle bundle = new Bundle();
+        if (data == null) return bundle;
+
+        if (data.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
+            bundle.putString(MediaMetadata.METADATA_KEY_TITLE,
+                    data.getString(MediaMetadata.METADATA_KEY_TITLE));
+        }
+
+        if (data.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
+            bundle.putString(MediaMetadata.METADATA_KEY_ARTIST,
+                    data.getString(MediaMetadata.METADATA_KEY_ARTIST));
+        }
+
+        if (data.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
+            bundle.putString(MediaMetadata.METADATA_KEY_ALBUM,
+                    data.getString(MediaMetadata.METADATA_KEY_ALBUM));
+        }
+
+        if (data.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
+            bundle.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
+                    data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
+        }
+
+        if (data.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
+            bundle.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
+                    data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
+        }
+
+        if (data.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
+            bundle.putString(MediaMetadata.METADATA_KEY_GENRE,
+                    data.getString(MediaMetadata.METADATA_KEY_GENRE));
+        }
+
+        if (data.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+            bundle.putLong(MediaMetadata.METADATA_KEY_DURATION,
+                    data.getLong(MediaMetadata.METADATA_KEY_DURATION));
+        }
+
+        return bundle;
+    }
+
+    public static Metadata toMetadata(MediaSession.QueueItem item) {
+        if (item == null) {
+            return empty_data();
+        }
+
+        Bundle bundle = descriptionToBundle(item.getDescription());
+
+        if (DEBUG) {
+            for (String key : bundle.keySet()) {
+                Log.d(TAG, "toMetadata: QueueItem: ContainsKey: " + key);
+            }
+        }
+
+        Metadata ret = bundleToMetadata(bundle);
+
+        // For Queue Items, the Media Id will always be just its Queue ID
+        // We don't need to use its actual ID since we don't promise UIDS being valid
+        // between a file system and it's now playing list.
+        ret.mediaId = NOW_PLAYING_PREFIX + item.getQueueId();
+
+        return ret;
+    }
+
+    public static Metadata toMetadata(MediaMetadata data) {
+        if (data == null) {
+            return empty_data();
+        }
+
+        MediaDescription desc = data.getDescription();
+
+        Bundle dataBundle = mediaMetadataToBundle(data);
+        Bundle bundle = descriptionToBundle(data.getDescription());
+
+        // Prioritize the media metadata over the media description
+        bundle.putAll(dataBundle);
+
+        if (DEBUG) {
+            for (String key : bundle.keySet()) {
+                Log.d(TAG, "toMetadata: MediaMetadata: ContainsKey: " + key);
+            }
+        }
+
+        Metadata ret = bundleToMetadata(bundle);
+
+        // This will always be currsong. The AVRCP service will overwrite the mediaId if it needs to
+        // TODO (apanicke): Remove when the service is ready, right now it makes debugging much more
+        // convenient
+        ret.mediaId = "currsong";
+
+        return ret;
+    }
+
+    public static Metadata toMetadata(MediaItem item) {
+        if (item == null) {
+            return empty_data();
+        }
+
+        Bundle bundle = descriptionToBundle(item.getDescription());
+
+        if (DEBUG) {
+            for (String key : bundle.keySet()) {
+                Log.d(TAG, "toMetadata: MediaItem: ContainsKey: " + key);
+            }
+        }
+
+        Metadata ret = bundleToMetadata(bundle);
+        ret.mediaId = item.getMediaId();
+
+        return ret;
+    }
+
+    public static List<Metadata> toMetadataList(List<MediaSession.QueueItem> items) {
+        ArrayList<Metadata> list = new ArrayList<Metadata>();
+
+        if (items == null) return list;
+
+        for (int i = 0; i < items.size(); i++) {
+            Metadata data = toMetadata(items.get(i));
+            data.trackNum = "" + (i + 1);
+            data.numTracks = "" + items.size();
+            list.add(data);
+        }
+
+        return list;
+    }
+
+    // Helper method to close a list of ListItems so that if the callee wants
+    // to mutate the list they can do it without affecting any internally cached info
+    public static List<ListItem> cloneList(List<ListItem> list) {
+        List<ListItem> clone = new ArrayList<ListItem>(list.size());
+        for (ListItem item : list) clone.add(item.clone());
+        return clone;
+    }
+
+    public static String getDisplayName(Context context, String packageName) {
+        try {
+            PackageManager manager = context.getPackageManager();
+            return manager.getApplicationLabel(manager.getApplicationInfo(packageName, 0))
+                    .toString();
+        } catch (Exception e) {
+            Log.w(TAG, "Name Not Found using package name: " + packageName);
+            return packageName;
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBatch.java b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
index a74d100..a66a667 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBatch.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
@@ -32,9 +32,6 @@
 
 package com.android.bluetooth.opp;
 
-import java.io.File;
-import java.util.ArrayList;
-
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
@@ -42,6 +39,9 @@
 
 import com.google.android.collect.Lists;
 
+import java.io.File;
+import java.util.ArrayList;
+
 /**
  * This class stores information about a batch of OPP shares that should be
  * transferred in one session.
@@ -83,18 +83,18 @@
          * Called to notify when a share is added into the batch
          * @param id , BluetoothOppShareInfo.id
          */
-        public void onShareAdded(int id);
+        void onShareAdded(int id);
 
         /**
          * Called to notify when a share is deleted from the batch
          * @param id , BluetoothOppShareInfo.id
          */
-        public void onShareDeleted(int id);
+        void onShareDeleted(int id);
 
         /**
          * Called to notify when the batch is canceled
          */
-        public void onBatchCanceled();
+        void onBatchCanceled();
     }
 
     /**
@@ -112,7 +112,9 @@
         mStatus = Constants.BATCH_STATUS_PENDING;
         mShares.add(info);
 
-        if (V) Log.v(TAG, "New Batch created for info " + info.mId);
+        if (V) {
+            Log.v(TAG, "New Batch created for info " + info.mId);
+        }
     }
 
     /**
@@ -121,7 +123,6 @@
     /* There are 2 cases: Service scans the databases and it's multiple send
      * Service receives database update and know additional file should be received
      */
-
     public void addShare(BluetoothOppShareInfo info) {
         mShares.add(info);
         if (mListener != null) {
@@ -137,7 +138,9 @@
      * 3) update ContentProvider for these canceled transfer
      */
     public void cancelBatch() {
-        if (V) Log.v(TAG, "batch " + this.mId + " is canceled");
+        if (V) {
+            Log.v(TAG, "batch " + this.mId + " is canceled");
+        }
 
         if (mListener != null) {
             mListener.onBatchCanceled();
@@ -150,7 +153,9 @@
                 if (info.mDirection == BluetoothShare.DIRECTION_INBOUND && info.mFilename != null) {
                     new File(info.mFilename).delete();
                 }
-                if (V) Log.v(TAG, "Cancel batch for info " + info.mId);
+                if (V) {
+                    Log.v(TAG, "Cancel batch for info " + info.mId);
+                }
 
                 Constants.updateShareStatus(mContext, info.mId, BluetoothShare.STATUS_CANCELED);
             }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
index efc62b0..0253e24 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
@@ -32,8 +32,6 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
@@ -41,14 +39,15 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.bluetooth.R;
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 
 /**
  * This class is designed to show BT enable confirmation dialog;
  */
-public class BluetoothOppBtEnableActivity extends AlertActivity implements
-        DialogInterface.OnClickListener {
+public class BluetoothOppBtEnableActivity extends AlertActivity
+        implements DialogInterface.OnClickListener {
     private BluetoothOppManager mOppManager;
 
     @Override
@@ -71,13 +70,15 @@
 
     private View createView() {
         View view = getLayoutInflater().inflate(R.layout.confirm_dialog, null);
-        TextView contentView = (TextView)view.findViewById(R.id.content);
-        contentView.setText(getString(R.string.bt_enable_line1) + "\n\n"
-                + getString(R.string.bt_enable_line2) + "\n");
+        TextView contentView = (TextView) view.findViewById(R.id.content);
+        contentView.setText(
+                getString(R.string.bt_enable_line1) + "\n\n" + getString(R.string.bt_enable_line2)
+                        + "\n");
 
         return view;
     }
 
+    @Override
     public void onClick(DialogInterface dialog, int which) {
         switch (which) {
             case DialogInterface.BUTTON_POSITIVE:
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtEnablingActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtEnablingActivity.java
index 8e12686..4a9a710 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtEnablingActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtEnablingActivity.java
@@ -32,8 +32,6 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -47,6 +45,7 @@
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.bluetooth.R;
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 
@@ -94,7 +93,7 @@
 
     private View createView() {
         View view = getLayoutInflater().inflate(R.layout.bt_enabling_progress, null);
-        TextView contentView = (TextView)view.findViewById(R.id.progress_info);
+        TextView contentView = (TextView) view.findViewById(R.id.progress_info);
         contentView.setText(getString(R.string.enabling_progress_content));
 
         return view;
@@ -103,7 +102,9 @@
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
-            if (D) Log.d(TAG, "onKeyDown() called; Key: back key");
+            if (D) {
+                Log.d(TAG, "onKeyDown() called; Key: back key");
+            }
             mTimeoutHandler.removeMessages(BT_ENABLING_TIMEOUT);
             cancelSendingProgress();
         }
@@ -123,7 +124,9 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case BT_ENABLING_TIMEOUT:
-                    if (V) Log.v(TAG, "Received BT_ENABLING_TIMEOUT msg.");
+                    if (V) {
+                        Log.v(TAG, "Received BT_ENABLING_TIMEOUT msg.");
+                    }
                     cancelSendingProgress();
                     break;
                 default:
@@ -136,7 +139,9 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (V) Log.v(TAG, "Received intent: " + action) ;
+            if (V) {
+                Log.v(TAG, "Received intent: " + action);
+            }
             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                 switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
                     case BluetoothAdapter.STATE_ON:
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java
index d2380f6..22a8d82 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java
@@ -32,22 +32,21 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.bluetooth.R;
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 
 /**
  * This class is designed to show BT error messages;
  */
-public class BluetoothOppBtErrorActivity extends AlertActivity implements
-        DialogInterface.OnClickListener {
+public class BluetoothOppBtErrorActivity extends AlertActivity
+        implements DialogInterface.OnClickListener {
 
     private String mErrorContent;
 
@@ -71,11 +70,12 @@
 
     private View createView() {
         View view = getLayoutInflater().inflate(R.layout.confirm_dialog, null);
-        TextView contentView = (TextView)view.findViewById(R.id.content);
+        TextView contentView = (TextView) view.findViewById(R.id.content);
         contentView.setText(mErrorContent);
         return view;
     }
 
+    @Override
     public void onClick(DialogInterface dialog, int which) {
         switch (which) {
             case DialogInterface.BUTTON_POSITIVE:
diff --git a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
index 3765210..166983a 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
@@ -25,6 +25,7 @@
 import android.os.UserManager;
 import android.support.v4.content.FileProvider;
 import android.util.Log;
+
 import java.io.File;
 
 /**
@@ -66,14 +67,16 @@
             if (!mRegisteredReceiver) {
                 IntentFilter userFilter = new IntentFilter();
                 userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
-                mContext.registerReceiverAsUser(
-                        mBroadcastReceiver, UserHandle.CURRENT, userFilter, null, null);
+                mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.CURRENT, userFilter,
+                        null, null);
                 mRegisteredReceiver = true;
             }
             UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
             if (userManager.isUserUnlocked()) {
                 if (!mInitialized) {
-                    if (Constants.DEBUG) Log.d(TAG, "Initialized");
+                    if (Constants.DEBUG) {
+                        Log.d(TAG, "Initialized");
+                    }
                     super.attachInfo(mContext, mProviderInfo);
                     mInitialized = true;
                 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
index dd4dc3c..6e83480 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
@@ -26,27 +26,31 @@
 import java.util.ArrayList;
 
 public class BluetoothOppHandoverReceiver extends BroadcastReceiver {
-    public static final String TAG ="BluetoothOppHandoverReceiver";
+    public static final String TAG = "BluetoothOppHandoverReceiver";
     private static final boolean D = Constants.DEBUG;
 
     @Override
     public void onReceive(Context context, Intent intent) {
         String action = intent.getAction();
 
-        if (action.equals(Constants.ACTION_HANDOVER_SEND) ||
-               action.equals(Constants.ACTION_HANDOVER_SEND_MULTIPLE)) {
+        if (action.equals(Constants.ACTION_HANDOVER_SEND) || action.equals(
+                Constants.ACTION_HANDOVER_SEND_MULTIPLE)) {
             final BluetoothDevice device =
                     (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
             if (device == null) {
-                if (D) Log.d(TAG, "No device attached to handover intent.");
+                if (D) {
+                    Log.d(TAG, "No device attached to handover intent.");
+                }
                 return;
             }
 
             final String mimeType = intent.getType();
             ArrayList<Uri> uris = new ArrayList<Uri>();
             if (action.equals(Constants.ACTION_HANDOVER_SEND)) {
-                Uri stream = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
-                if (stream != null) uris.add(stream);
+                Uri stream = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
+                if (stream != null) {
+                    uris.add(stream);
+                }
             } else if (action.equals(Constants.ACTION_HANDOVER_SEND_MULTIPLE)) {
                 uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
             }
@@ -55,6 +59,7 @@
                 final Context finalContext = context;
                 final ArrayList<Uri> finalUris = uris;
                 Thread t = new Thread(new Runnable() {
+                    @Override
                     public void run() {
                         BluetoothOppManager.getInstance(finalContext)
                                 .saveSendingFileInfo(mimeType, finalUris, true /* isHandover */,
@@ -64,25 +69,35 @@
                 });
                 t.start();
             } else {
-                if (D) Log.d(TAG, "No mimeType or stream attached to handover request");
+                if (D) {
+                    Log.d(TAG, "No mimeType or stream attached to handover request");
+                }
                 return;
             }
         } else if (action.equals(Constants.ACTION_WHITELIST_DEVICE)) {
             BluetoothDevice device =
-                    (BluetoothDevice)intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            if (D) Log.d(TAG, "Adding " + device + " to whitelist");
-            if (device == null) return;
+                    (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            if (D) {
+                Log.d(TAG, "Adding " + device + " to whitelist");
+            }
+            if (device == null) {
+                return;
+            }
             BluetoothOppManager.getInstance(context).addToWhitelist(device.getAddress());
         } else if (action.equals(Constants.ACTION_STOP_HANDOVER)) {
             int id = intent.getIntExtra(Constants.EXTRA_BT_OPP_TRANSFER_ID, -1);
             if (id != -1) {
                 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);
 
-                if (D) Log.d(TAG, "Stopping handover transfer with Uri " + contentUri);
+                if (D) {
+                    Log.d(TAG, "Stopping handover transfer with Uri " + contentUri);
+                }
                 context.getContentResolver().delete(contentUri, null, null);
             }
         } else {
-            if (D) Log.d(TAG, "Unknown action: " + action);
+            if (D) {
+                Log.d(TAG, "Unknown action: " + action);
+            }
         }
     }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
index 3a39439..1b5ffe1 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
@@ -32,8 +32,6 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -44,21 +42,22 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.text.format.Formatter;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
 import android.widget.TextView;
 import android.widget.Toast;
-import android.text.format.Formatter;
 
+import com.android.bluetooth.R;
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 
 /**
  * This class is designed to ask user to confirm if accept incoming file;
  */
-public class BluetoothOppIncomingFileConfirmActivity extends AlertActivity implements
-        DialogInterface.OnClickListener {
+public class BluetoothOppIncomingFileConfirmActivity extends AlertActivity
+        implements DialogInterface.OnClickListener {
     private static final String TAG = "BluetoothIncomingFileConfirmActivity";
     private static final boolean D = Constants.DEBUG;
     private static final boolean V = Constants.VERBOSE;
@@ -90,7 +89,9 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         setTheme(R.style.Theme_Material_Settings_Floating);
-        if (V) Log.d(TAG, "onCreate(): action = " + getIntent().getAction());
+        if (V) {
+            Log.d(TAG, "onCreate(): action = " + getIntent().getAction());
+        }
         super.onCreate(savedInstanceState);
 
         Intent intent = getIntent();
@@ -98,7 +99,9 @@
         mTransInfo = new BluetoothOppTransferInfo();
         mTransInfo = BluetoothOppUtility.queryRecord(this, mUri);
         if (mTransInfo == null) {
-            if (V) Log.e(TAG, "Error: Can not get data from db");
+            if (V) {
+                Log.e(TAG, "Error: Can not get data from db");
+            }
             finish();
             return;
         }
@@ -112,28 +115,33 @@
         p.mNegativeButtonText = getString(R.string.incoming_file_confirm_cancel);
         p.mNegativeButtonListener = this;
         setupAlert();
-        if (V) Log.v(TAG, "mTimeout: " + mTimeout);
+        if (V) {
+            Log.v(TAG, "mTimeout: " + mTimeout);
+        }
         if (mTimeout) {
             onTimeout();
         }
 
-        if (V) Log.v(TAG, "BluetoothIncomingFileConfirmActivity: Got uri:" + mUri);
+        if (V) {
+            Log.v(TAG, "BluetoothIncomingFileConfirmActivity: Got uri:" + mUri);
+        }
 
-        registerReceiver(mReceiver, new IntentFilter(
-                BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION));
+        registerReceiver(mReceiver,
+                new IntentFilter(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION));
     }
 
     private View createView() {
         View view = getLayoutInflater().inflate(R.layout.incoming_dialog, null);
 
-        ((TextView)view.findViewById(R.id.from_content)).setText(mTransInfo.mDeviceName);
-        ((TextView)view.findViewById(R.id.filename_content)).setText(mTransInfo.mFileName);
-        ((TextView)view.findViewById(R.id.size_content)).setText(
+        ((TextView) view.findViewById(R.id.from_content)).setText(mTransInfo.mDeviceName);
+        ((TextView) view.findViewById(R.id.filename_content)).setText(mTransInfo.mFileName);
+        ((TextView) view.findViewById(R.id.size_content)).setText(
                 Formatter.formatFileSize(this, mTransInfo.mTotalBytes));
 
         return view;
     }
 
+    @Override
     public void onClick(DialogInterface dialog, int which) {
         switch (which) {
             case DialogInterface.BUTTON_POSITIVE:
@@ -161,12 +169,9 @@
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
-            if (D) Log.d(TAG, "onKeyDown() called; Key: back key");
-            mUpdateValues = new ContentValues();
-            mUpdateValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_HIDDEN);
-            this.getContentResolver().update(mUri, mUpdateValues, null, null);
-
-            Toast.makeText(this, getString(R.string.bt_toast_2), Toast.LENGTH_SHORT).show();
+            if (D) {
+                Log.d(TAG, "onKeyDown() called; Key: back key");
+            }
             finish();
             return true;
         }
@@ -183,7 +188,9 @@
     protected void onRestoreInstanceState(Bundle savedInstanceState) {
         super.onRestoreInstanceState(savedInstanceState);
         mTimeout = savedInstanceState.getBoolean(PREFERENCE_USER_TIMEOUT);
-        if (V) Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout);
+        if (V) {
+            Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout);
+        }
         if (mTimeout) {
             onTimeout();
         }
@@ -192,17 +199,19 @@
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        if (V) Log.v(TAG, "onSaveInstanceState() mTimeout: " + mTimeout);
+        if (V) {
+            Log.v(TAG, "onSaveInstanceState() mTimeout: " + mTimeout);
+        }
         outState.putBoolean(PREFERENCE_USER_TIMEOUT, mTimeout);
     }
 
     private void onTimeout() {
         mTimeout = true;
-        mAlert.setTitle(getString(R.string.incoming_file_confirm_timeout_content,
-                mTransInfo.mDeviceName));
+        mAlert.setTitle(
+                getString(R.string.incoming_file_confirm_timeout_content, mTransInfo.mDeviceName));
         mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-        mAlert.getButton(DialogInterface.BUTTON_POSITIVE).setText(
-                getString(R.string.incoming_file_confirm_timeout_ok));
+        mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
+                .setText(getString(R.string.incoming_file_confirm_timeout_ok));
 
         mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(DISMISS_TIMEOUT_DIALOG),
                 DISMISS_TIMEOUT_DIALOG_VALUE);
@@ -213,7 +222,9 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case DISMISS_TIMEOUT_DIALOG:
-                    if (V) Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
+                    if (V) {
+                        Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
+                    }
                     finish();
                     break;
                 default:
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index c638f6f..99e3e69 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -32,6 +32,18 @@
 
 package com.android.bluetooth.opp;
 
+import android.app.Activity;
+import android.bluetooth.BluetoothDevicePicker;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Patterns;
+import android.widget.Toast;
+
 import com.android.bluetooth.R;
 
 import java.io.File;
@@ -39,21 +51,9 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Locale;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.Locale;
-
-import android.app.Activity;
-import android.bluetooth.BluetoothDevicePicker;
-import android.content.Intent;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.util.Log;
-import android.util.Patterns;
-import android.widget.Toast;
 
 /**
  * This class is designed to act as the entry point of handling the share intent
@@ -96,32 +96,40 @@
             if (action.equals(Intent.ACTION_SEND)) {
                 // TODO: handle type == null case
                 final String type = intent.getType();
-                final Uri stream = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
-                CharSequence extra_text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
+                final Uri stream = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
+                CharSequence extraText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
                 // If we get ACTION_SEND intent with EXTRA_STREAM, we'll use the
                 // uri data;
                 // If we get ACTION_SEND intent without EXTRA_STREAM, but with
                 // EXTRA_TEXT, we will try send this TEXT out; Currently in
                 // Browser, share one link goes to this case;
                 if (stream != null && type != null) {
-                    if (V) Log.v(TAG, "Get ACTION_SEND intent: Uri = " + stream + "; mimetype = "
-                                + type);
+                    if (V) {
+                        Log.v(TAG,
+                                "Get ACTION_SEND intent: Uri = " + stream + "; mimetype = " + type);
+                    }
                     // Save type/stream, will be used when adding transfer
                     // session to DB.
                     Thread t = new Thread(new Runnable() {
+                        @Override
                         public void run() {
-                            sendFileInfo(type, stream.toString(), false /* isHandover */,
-                                    true /* fromExternal */);
+                            sendFileInfo(type, stream.toString(), false /* isHandover */, true /*
+                             fromExternal */);
                         }
                     });
                     t.start();
                     return;
-                } else if (extra_text != null && type != null) {
-                    if (V) Log.v(TAG, "Get ACTION_SEND intent with Extra_text = "
-                                + extra_text.toString() + "; mimetype = " + type);
-                    final Uri fileUri = creatFileForSharedContent(this.createCredentialProtectedStorageContext(), extra_text);
+                } else if (extraText != null && type != null) {
+                    if (V) {
+                        Log.v(TAG,
+                                "Get ACTION_SEND intent with Extra_text = " + extraText.toString()
+                                        + "; mimetype = " + type);
+                    }
+                    final Uri fileUri = creatFileForSharedContent(
+                            this.createCredentialProtectedStorageContext(), extraText);
                     if (fileUri != null) {
                         Thread t = new Thread(new Runnable() {
+                            @Override
                             public void run() {
                                 sendFileInfo(type, fileUri.toString(), false /* isHandover */,
                                         false /* fromExternal */);
@@ -130,7 +138,7 @@
                         t.start();
                         return;
                     } else {
-                        Log.w(TAG,"Error trying to do set text...File not created!");
+                        Log.w(TAG, "Error trying to do set text...File not created!");
                         finish();
                         return;
                     }
@@ -143,9 +151,12 @@
                 final String mimeType = intent.getType();
                 final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
                 if (mimeType != null && uris != null) {
-                    if (V) Log.v(TAG, "Get ACTION_SHARE_MULTIPLE intent: uris " + uris + "\n Type= "
+                    if (V) {
+                        Log.v(TAG, "Get ACTION_SHARE_MULTIPLE intent: uris " + uris + "\n Type= "
                                 + mimeType);
+                    }
                     Thread t = new Thread(new Runnable() {
+                        @Override
                         public void run() {
                             try {
                                 BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
@@ -171,7 +182,9 @@
             }
         } else if (action.equals(Constants.ACTION_OPEN)) {
             Uri uri = getIntent().getData();
-            if (V) Log.v(TAG, "Get ACTION_OPEN intent: Uri = " + uri);
+            if (V) {
+                Log.v(TAG, "Get ACTION_OPEN intent: Uri = " + uri);
+            }
 
             Intent intent1 = new Intent();
             intent1.setAction(action);
@@ -189,32 +202,38 @@
      * Turns on Bluetooth if not already on, or launches device picker if Bluetooth is on
      * @return
      */
-    private final void launchDevicePicker() {
+    private void launchDevicePicker() {
         // TODO: In the future, we may send intent to DevicePickerActivity
         // directly,
         // and let DevicePickerActivity to handle Bluetooth Enable.
         if (!BluetoothOppManager.getInstance(this).isEnabled()) {
-            if (V) Log.v(TAG, "Prepare Enable BT!! ");
+            if (V) {
+                Log.v(TAG, "Prepare Enable BT!! ");
+            }
             Intent in = new Intent(this, BluetoothOppBtEnableActivity.class);
             in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startActivity(in);
         } else {
-            if (V) Log.v(TAG, "BT already enabled!! ");
+            if (V) {
+                Log.v(TAG, "BT already enabled!! ");
+            }
             Intent in1 = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
             in1.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             in1.putExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
             in1.putExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
                     BluetoothDevicePicker.FILTER_TYPE_TRANSFER);
-            in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE,
-                    Constants.THIS_PACKAGE_NAME);
+            in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE, Constants.THIS_PACKAGE_NAME);
             in1.putExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS,
                     BluetoothOppReceiver.class.getName());
-            if (V) {Log.d(TAG,"Launching " +BluetoothDevicePicker.ACTION_LAUNCH );}
+            if (V) {
+                Log.d(TAG, "Launching " + BluetoothDevicePicker.ACTION_LAUNCH);
+            }
             startActivity(in1);
         }
     }
+
     /* Returns true if Bluetooth is allowed given current airplane mode settings. */
-    private final boolean isBluetoothAllowed() {
+    private boolean isBluetoothAllowed() {
         final ContentResolver resolver = this.getContentResolver();
 
         // Check if airplane mode is on
@@ -227,18 +246,19 @@
         // Check if airplane mode matters
         final String airplaneModeRadios =
                 Settings.System.getString(resolver, Settings.Global.AIRPLANE_MODE_RADIOS);
-        final boolean isAirplaneSensitive = airplaneModeRadios == null ? true :
-                airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
+        final boolean isAirplaneSensitive =
+                airplaneModeRadios == null || airplaneModeRadios.contains(
+                        Settings.System.RADIO_BLUETOOTH);
         if (!isAirplaneSensitive) {
             return true;
         }
 
         // Check if Bluetooth may be enabled in airplane mode
-        final String airplaneModeToggleableRadios = Settings.System.getString(
-                resolver, Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
-        final boolean isAirplaneToggleable = airplaneModeToggleableRadios == null
-                ? false
-                : airplaneModeToggleableRadios.contains(Settings.Global.RADIO_BLUETOOTH);
+        final String airplaneModeToggleableRadios = Settings.System.getString(resolver,
+                Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+        final boolean isAirplaneToggleable =
+                airplaneModeToggleableRadios != null && airplaneModeToggleableRadios.contains(
+                        Settings.Global.RADIO_BLUETOOTH);
         if (isAirplaneToggleable) {
             return true;
         }
@@ -269,10 +289,9 @@
             // Regex that matches Web URL protocol part as case insensitive.
             Pattern webUrlProtocol = Pattern.compile("(?i)(http|https)://");
 
-            Pattern pattern = Pattern.compile("("
-                    + Patterns.WEB_URL.pattern() + ")|("
-                    + Patterns.EMAIL_ADDRESS.pattern() + ")|("
-                    + Patterns.PHONE.pattern() + ")");
+            Pattern pattern = Pattern.compile(
+                    "(" + Patterns.WEB_URL.pattern() + ")|(" + Patterns.EMAIL_ADDRESS.pattern()
+                            + ")|(" + Patterns.PHONE.pattern() + ")");
             // Find any embedded URL's and linkify
             Matcher m = pattern.matcher(text);
             while (m.find()) {
@@ -285,19 +304,19 @@
                     if (proto.find()) {
                         // This is work around to force URL protocol part be lower case,
                         // because WebView could follow only lower case protocol link.
-                        link = proto.group().toLowerCase(Locale.US) +
-                                matchStr.substring(proto.end());
+                        link = proto.group().toLowerCase(Locale.US) + matchStr.substring(
+                                proto.end());
                     } else {
                         // Patterns.WEB_URL matches URL without protocol part,
                         // so added default protocol to link.
                         link = "http://" + matchStr;
                     }
 
-                // Find any embedded email address
+                    // Find any embedded email address
                 } else if (Patterns.EMAIL_ADDRESS.matcher(matchStr).matches()) {
                     link = "mailto:" + matchStr;
 
-                // Find any embedded phone numbers and linkify
+                    // Find any embedded phone numbers and linkify
                 } else if (Patterns.PHONE.matcher(matchStr).matches()) {
                     link = "tel:" + matchStr;
                 }
@@ -316,8 +335,9 @@
                 outStream.write(byteBuff, 0, byteBuff.length);
                 fileUri = Uri.fromFile(new File(context.getFilesDir(), fileName));
                 if (fileUri != null) {
-                    if (D) Log.d(TAG, "Created one file for shared content: "
-                            + fileUri.toString());
+                    if (D) {
+                        Log.d(TAG, "Created one file for shared content: " + fileUri.toString());
+                    }
                 }
             }
         } catch (FileNotFoundException e) {
@@ -379,8 +399,8 @@
         return text;
     }
 
-    private void sendFileInfo(
-            String mimeType, String uriString, boolean isHandover, boolean fromExternal) {
+    private void sendFileInfo(String mimeType, String uriString, boolean isHandover,
+            boolean fromExternal) {
         BluetoothOppManager manager = BluetoothOppManager.getInstance(getApplicationContext());
         try {
             manager.saveSendingFileInfo(mimeType, uriString, isHandover, fromExternal);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppManager.java b/src/com/android/bluetooth/opp/BluetoothOppManager.java
index 45e0578..129d110 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppManager.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppManager.java
@@ -32,8 +32,6 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.ContentResolver;
@@ -48,6 +46,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.bluetooth.R;
+
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -61,10 +61,10 @@
     private static final String TAG = "BluetoothOppManager";
     private static final boolean V = Constants.VERBOSE;
 
-    private static BluetoothOppManager INSTANCE;
+    private static BluetoothOppManager sInstance;
 
     /** Used when obtaining a reference to the singleton instance. */
-    private static Object INSTANCE_LOCK = new Object();
+    private static final Object INSTANCE_LOCK = new Object();
 
     private boolean mInitialized;
 
@@ -106,13 +106,13 @@
 
     public boolean mMultipleFlag;
 
-    private int mfileNumInBatch;
+    private int mFileNumInBatch;
 
     private int mInsertShareThreadNum = 0;
 
     // A list of devices that may send files over OPP to this device
     // without user confirmation. Used for connection handover from forex NFC.
-    private List<Pair<String,Long> > mWhitelist = new ArrayList<Pair<String, Long> >();
+    private List<Pair<String, Long>> mWhitelist = new ArrayList<Pair<String, Long>>();
 
     // The time for which the whitelist entries remain valid.
     private static final int WHITELIST_DURATION_MS = 15000;
@@ -122,12 +122,12 @@
      */
     public static BluetoothOppManager getInstance(Context context) {
         synchronized (INSTANCE_LOCK) {
-            if (INSTANCE == null) {
-                INSTANCE = new BluetoothOppManager();
+            if (sInstance == null) {
+                sInstance = new BluetoothOppManager();
             }
-            INSTANCE.init(context);
+            sInstance.init(context);
 
-            return INSTANCE;
+            return sInstance;
         }
     }
 
@@ -135,15 +135,18 @@
      * init
      */
     private boolean init(Context context) {
-        if (mInitialized)
+        if (mInitialized) {
             return true;
+        }
         mInitialized = true;
 
         mContext = context;
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         if (mAdapter == null) {
-            if (V) Log.v(TAG, "BLUETOOTH_SERVICE is not started! ");
+            if (V) {
+                Log.v(TAG, "BLUETOOTH_SERVICE is not started! ");
+            }
         }
 
         // Restore data from preference
@@ -156,20 +159,24 @@
     private void cleanupWhitelist() {
         // Removes expired entries
         long curTime = SystemClock.elapsedRealtime();
-        for (Iterator<Pair<String,Long>> iter = mWhitelist.iterator(); iter.hasNext(); ) {
-            Pair<String,Long> entry = iter.next();
+        for (Iterator<Pair<String, Long>> iter = mWhitelist.iterator(); iter.hasNext(); ) {
+            Pair<String, Long> entry = iter.next();
             if (curTime - entry.second > WHITELIST_DURATION_MS) {
-                if (V) Log.v(TAG, "Cleaning out whitelist entry " + entry.first);
+                if (V) {
+                    Log.v(TAG, "Cleaning out whitelist entry " + entry.first);
+                }
                 iter.remove();
             }
         }
     }
 
     public synchronized void addToWhitelist(String address) {
-        if (address == null) return;
+        if (address == null) {
+            return;
+        }
         // Remove any existing entries
-        for (Iterator<Pair<String,Long>> iter = mWhitelist.iterator(); iter.hasNext(); ) {
-            Pair<String,Long> entry = iter.next();
+        for (Iterator<Pair<String, Long>> iter = mWhitelist.iterator(); iter.hasNext(); ) {
+            Pair<String, Long> entry = iter.next();
             if (entry.first.equals(address)) {
                 iter.remove();
             }
@@ -179,8 +186,10 @@
 
     public synchronized boolean isWhitelisted(String address) {
         cleanupWhitelist();
-        for (Pair<String,Long> entry : mWhitelist) {
-            if (entry.first.equals(address)) return true;
+        for (Pair<String, Long> entry : mWhitelist) {
+            if (entry.first.equals(address)) {
+                return true;
+            }
         }
         return false;
     }
@@ -198,8 +207,10 @@
         mMimeTypeOfSendingFiles = settings.getString(MIME_TYPE_MULTIPLE, null);
         mMultipleFlag = settings.getBoolean(MULTIPLE_FLAG, false);
 
-        if (V) Log.v(TAG, "restoreApplicationData! " + mSendingFlag + mMultipleFlag
+        if (V) {
+            Log.v(TAG, "restoreApplicationData! " + mSendingFlag + mMultipleFlag
                     + mMimeTypeOfSendingFile + mUriOfSendingFile);
+        }
 
         String strUris = settings.getString(FILE_URIS, null);
         mUrisOfSendingFiles = new ArrayList<Uri>();
@@ -207,7 +218,9 @@
             String[] splitUri = strUris.split(ARRAYLIST_ITEM_SEPERATOR);
             for (int i = 0; i < splitUri.length; i++) {
                 mUrisOfSendingFiles.add(Uri.parse(splitUri[i]));
-                if (V) Log.v(TAG, "Uri in batch:  " + Uri.parse(splitUri[i]));
+                if (V) {
+                    Log.v(TAG, "Uri in batch:  " + Uri.parse(splitUri[i]));
+                }
             }
         }
 
@@ -218,8 +231,8 @@
      * Save application data to preference, need restore these data when service restart
      */
     private void storeApplicationData() {
-        SharedPreferences.Editor editor = mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0)
-                .edit();
+        SharedPreferences.Editor editor =
+                mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0).edit();
         editor.putBoolean(SENDING_FLAG, mSendingFlag);
         editor.putBoolean(MULTIPLE_FLAG, mMultipleFlag);
         if (mMultipleFlag) {
@@ -243,7 +256,9 @@
             editor.remove(FILE_URIS);
         }
         editor.apply();
-        if (V) Log.v(TAG, "Application data stored to SharedPreference! ");
+        if (V) {
+            Log.v(TAG, "Application data stored to SharedPreference! ");
+        }
     }
 
     public void saveSendingFileInfo(String mimeType, String uriString, boolean isHandover,
@@ -251,12 +266,14 @@
         synchronized (BluetoothOppManager.this) {
             mMultipleFlag = false;
             mMimeTypeOfSendingFile = mimeType;
-            mUriOfSendingFile = uriString;
             mIsHandoverInitiated = isHandover;
             Uri uri = Uri.parse(uriString);
-            BluetoothOppUtility.putSendFileInfo(
-                    uri, BluetoothOppSendFileInfo.generateFileInfo(
-                                 mContext, uri, mimeType, fromExternal));
+            BluetoothOppSendFileInfo sendFileInfo =
+                    BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType,
+                    fromExternal);
+            uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
+            BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
+            mUriOfSendingFile = uri.toString();
             storeApplicationData();
         }
     }
@@ -266,12 +283,15 @@
         synchronized (BluetoothOppManager.this) {
             mMultipleFlag = true;
             mMimeTypeOfSendingFiles = mimeType;
-            mUrisOfSendingFiles = uris;
+            mUrisOfSendingFiles = new ArrayList<Uri>();
             mIsHandoverInitiated = isHandover;
             for (Uri uri : uris) {
-                BluetoothOppUtility.putSendFileInfo(
-                        uri, BluetoothOppSendFileInfo.generateFileInfo(
-                                     mContext, uri, mimeType, fromExternal));
+                BluetoothOppSendFileInfo sendFileInfo =
+                        BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType,
+                        fromExternal);
+                uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
+                mUrisOfSendingFiles.add(uri);
+                BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
             }
             storeApplicationData();
         }
@@ -285,7 +305,9 @@
         if (mAdapter != null) {
             return mAdapter.isEnabled();
         } else {
-            if (V) Log.v(TAG, "BLUETOOTH_SERVICE is not available! ");
+            if (V) {
+                Log.v(TAG, "BLUETOOTH_SERVICE is not available! ");
+            }
             return false;
         }
     }
@@ -312,12 +334,13 @@
      * Get device name per bluetooth address.
      */
     public String getDeviceName(BluetoothDevice device) {
-        String deviceName;
+        String deviceName = null;
 
-        deviceName = BluetoothOppPreference.getInstance(mContext).getName(device);
-
-        if (deviceName == null && mAdapter != null) {
-            deviceName = device.getName();
+        if (device != null) {
+            deviceName = device.getAliasName();
+            if (deviceName == null) {
+                deviceName = BluetoothOppPreference.getInstance(mContext).getName(device);
+            }
         }
 
         if (deviceName == null) {
@@ -329,7 +352,7 @@
 
     public int getBatchSize() {
         synchronized (BluetoothOppManager.this) {
-            return mfileNumInBatch;
+            return mFileNumInBatch;
         }
     }
 
@@ -337,7 +360,9 @@
      * Fork a thread to insert share info to db.
      */
     public void startTransfer(BluetoothDevice device) {
-        if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);
+        if (V) {
+            Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);
+        }
         InsertShareInfoThread insertThread;
         synchronized (BluetoothOppManager.this) {
             if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) {
@@ -356,7 +381,7 @@
                     mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,
                     mIsHandoverInitiated);
             if (mMultipleFlag) {
-                mfileNumInBatch = mUrisOfSendingFiles.size();
+                mFileNumInBatch = mUrisOfSendingFiles.size();
             }
         }
 
@@ -386,9 +411,9 @@
 
         private final boolean mIsHandoverInitiated;
 
-        public InsertShareInfoThread(BluetoothDevice device, boolean multiple,
-                String typeOfSingleFile, String uri, String typeOfMultipleFiles,
-                ArrayList<Uri> uris, boolean handoverInitiated) {
+        InsertShareInfoThread(BluetoothDevice device, boolean multiple, String typeOfSingleFile,
+                String uri, String typeOfMultipleFiles, ArrayList<Uri> uris,
+                boolean handoverInitiated) {
             super("Insert ShareInfo Thread");
             this.mRemoteDevice = device;
             this.mIsMultiple = multiple;
@@ -402,7 +427,9 @@
                 mInsertShareThreadNum++;
             }
 
-            if (V) Log.v(TAG, "Thread id is: " + this.getId());
+            if (V) {
+                Log.v(TAG, "Thread id is: " + this.getId());
+            }
         }
 
         @Override
@@ -430,15 +457,18 @@
             Long ts = System.currentTimeMillis();
             for (int i = 0; i < count; i++) {
                 Uri fileUri = mUris.get(i);
+                ContentValues values = new ContentValues();
+                values.put(BluetoothShare.URI, fileUri.toString());
                 ContentResolver contentResolver = mContext.getContentResolver();
+                fileUri = BluetoothOppUtility.originalUri(fileUri);
                 String contentType = contentResolver.getType(fileUri);
-                if (V) Log.v(TAG, "Got mimetype: " + contentType + "  Got uri: " + fileUri);
+                if (V) {
+                    Log.v(TAG, "Got mimetype: " + contentType + "  Got uri: " + fileUri);
+                }
                 if (TextUtils.isEmpty(contentType)) {
                     contentType = mTypeOfMultipleFiles;
                 }
 
-                ContentValues values = new ContentValues();
-                values.put(BluetoothShare.URI, fileUri.toString());
                 values.put(BluetoothShare.MIMETYPE, contentType);
                 values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress());
                 values.put(BluetoothShare.TIMESTAMP, ts);
@@ -446,14 +476,16 @@
                     values.put(BluetoothShare.USER_CONFIRMATION,
                             BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
                 }
-                final Uri contentUri = mContext.getContentResolver().insert(
-                        BluetoothShare.CONTENT_URI, values);
-                if (V) Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: "
-                            + getDeviceName(mRemoteDevice));
+                final Uri contentUri =
+                        mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
+                if (V) {
+                    Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: " + getDeviceName(
+                            mRemoteDevice));
+                }
             }
         }
 
-         /**
+        /**
          * Insert single sending session to db, only used by Opp application.
          */
         private void insertSingleShare() {
@@ -465,16 +497,20 @@
                 values.put(BluetoothShare.USER_CONFIRMATION,
                         BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
             }
-            final Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI,
-                    values);
-            if (V) Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: "
-                                + getDeviceName(mRemoteDevice));
+            final Uri contentUri =
+                    mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
+            if (V) {
+                Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: " + getDeviceName(
+                        mRemoteDevice));
+            }
         }
     }
 
     void cleanUpSendingFileInfo() {
         synchronized (BluetoothOppManager.this) {
-            if (V) Log.v(TAG, "cleanUpSendingFileInfo: mMultipleFlag = " + mMultipleFlag);
+            if (V) {
+                Log.v(TAG, "cleanUpSendingFileInfo: mMultipleFlag = " + mMultipleFlag);
+            }
             if (!mMultipleFlag && (mUriOfSendingFile != null)) {
                 Uri uri = Uri.parse(mUriOfSendingFile);
                 BluetoothOppUtility.closeSendFileInfo(uri);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index 19d359d..41ffec3 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -32,23 +32,22 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.app.Notification;
-import android.app.Notification.Action;
-import android.app.NotificationManager;
-import android.app.NotificationChannel;
-import android.app.PendingIntent;
 import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
-import android.text.format.Formatter;
-import android.util.Log;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Process;
+import android.text.format.Formatter;
+import android.util.Log;
+
+import com.android.bluetooth.R;
 
 import java.util.HashMap;
 
@@ -61,34 +60,39 @@
     private static final String TAG = "BluetoothOppNotification";
     private static final boolean V = Constants.VERBOSE;
 
-    static final String status = "(" + BluetoothShare.STATUS + " == '192'" + ")";
+    static final String STATUS = "(" + BluetoothShare.STATUS + " == '192'" + ")";
 
-    static final String visible = "(" + BluetoothShare.VISIBILITY + " IS NULL OR "
-            + BluetoothShare.VISIBILITY + " == '" + BluetoothShare.VISIBILITY_VISIBLE + "'" + ")";
+    static final String VISIBLE =
+            "(" + BluetoothShare.VISIBILITY + " IS NULL OR " + BluetoothShare.VISIBILITY + " == '"
+                    + BluetoothShare.VISIBILITY_VISIBLE + "'" + ")";
 
-    static final String confirm = "(" + BluetoothShare.USER_CONFIRMATION + " == '"
+    static final String CONFIRM = "(" + BluetoothShare.USER_CONFIRMATION + " == '"
             + BluetoothShare.USER_CONFIRMATION_CONFIRMED + "' OR "
             + BluetoothShare.USER_CONFIRMATION + " == '"
-            + BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED  + "' OR "
+            + BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED + "' OR "
             + BluetoothShare.USER_CONFIRMATION + " == '"
             + BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED + "'" + ")";
 
-    static final String not_through_handover = "(" + BluetoothShare.USER_CONFIRMATION + " != '"
+    static final String NOT_THROUGH_HANDOVER = "(" + BluetoothShare.USER_CONFIRMATION + " != '"
             + BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED + "'" + ")";
 
-    static final String WHERE_RUNNING = status + " AND " + visible + " AND " + confirm;
+    static final String WHERE_RUNNING = STATUS + " AND " + VISIBLE + " AND " + CONFIRM;
 
-    static final String WHERE_COMPLETED = BluetoothShare.STATUS + " >= '200' AND " + visible +
-            " AND " + not_through_handover; // Don't show handover-initiated transfers
+    static final String WHERE_COMPLETED =
+            BluetoothShare.STATUS + " >= '200' AND " + VISIBLE + " AND " + NOT_THROUGH_HANDOVER;
+    // Don't show handover-initiated transfers
 
-    private static final String WHERE_COMPLETED_OUTBOUND = WHERE_COMPLETED + " AND " + "("
-            + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_OUTBOUND + ")";
+    private static final String WHERE_COMPLETED_OUTBOUND =
+            WHERE_COMPLETED + " AND " + "(" + BluetoothShare.DIRECTION + " == "
+                    + BluetoothShare.DIRECTION_OUTBOUND + ")";
 
-    private static final String WHERE_COMPLETED_INBOUND = WHERE_COMPLETED + " AND " + "("
-            + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_INBOUND + ")";
+    private static final String WHERE_COMPLETED_INBOUND =
+            WHERE_COMPLETED + " AND " + "(" + BluetoothShare.DIRECTION + " == "
+                    + BluetoothShare.DIRECTION_INBOUND + ")";
 
-    static final String WHERE_CONFIRM_PENDING = BluetoothShare.USER_CONFIRMATION + " == '"
-            + BluetoothShare.USER_CONFIRMATION_PENDING + "'" + " AND " + visible;
+    static final String WHERE_CONFIRM_PENDING =
+            BluetoothShare.USER_CONFIRMATION + " == '" + BluetoothShare.USER_CONFIRMATION_PENDING
+                    + "'" + " AND " + VISIBLE;
 
     public NotificationManager mNotificationMgr;
 
@@ -112,25 +116,27 @@
     private boolean mUpdateCompleteNotification = true;
 
     private ContentResolver mContentResolver = null;
+
     /**
      * This inner class is used to describe some properties for one transfer.
      */
     static class NotificationItem {
-        int id; // This first field _id in db;
+        public int id; // This first field _id in db;
 
-        int direction; // to indicate sending or receiving
+        public int direction; // to indicate sending or receiving
 
-        long totalCurrent = 0; // current transfer bytes
+        public long totalCurrent = 0; // current transfer bytes
 
-        long totalTotal = 0; // total bytes for current transfer
+        public long totalTotal = 0; // total bytes for current transfer
 
-        long timeStamp = 0; // Database time stamp. Used for sorting ongoing transfers.
+        public long timeStamp = 0; // Database time stamp. Used for sorting ongoing transfers.
 
-        String description; // the text above progress bar
+        public String description; // the text above progress bar
 
-        boolean handoverInitiated = false; // transfer initiated by connection handover (eg NFC)
+        public boolean handoverInitiated = false;
+        // transfer initiated by connection handover (eg NFC)
 
-        String destination; // destination associated with this transfer
+        public String destination; // destination associated with this transfer
     }
 
     /**
@@ -141,8 +147,8 @@
      */
     BluetoothOppNotification(Context ctx) {
         mContext = ctx;
-        mNotificationMgr = (NotificationManager)mContext
-                .getSystemService(Context.NOTIFICATION_SERVICE);
+        mNotificationMgr =
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         mNotificationChannel = new NotificationChannel(OPP_NOTIFICATION_CHANNEL,
                 mContext.getString(R.string.opp_notification_group),
                 NotificationManager.IMPORTANCE_HIGH);
@@ -160,11 +166,15 @@
         synchronized (BluetoothOppNotification.this) {
             mPendingUpdate++;
             if (mPendingUpdate > 1) {
-                if (V) Log.v(TAG, "update too frequent, put in queue");
+                if (V) {
+                    Log.v(TAG, "update too frequent, put in queue");
+                }
                 return;
             }
             if (!mHandler.hasMessages(NOTIFY)) {
-                if (V) Log.v(TAG, "send message");
+                if (V) {
+                    Log.v(TAG, "send message");
+                }
                 mHandler.sendMessage(mHandler.obtainMessage(NOTIFY));
             }
         }
@@ -179,29 +189,36 @@
     // 4. Handler checks if there are any more updates after 1 second.
     // 5. If there is an update, update it else stop.
     private Handler mHandler = new Handler() {
+        @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case NOTIFY:
                     synchronized (BluetoothOppNotification.this) {
                         if (mPendingUpdate > 0 && mUpdateNotificationThread == null) {
-                            if (V) Log.v(TAG, "new notify threadi!");
+                            if (V) {
+                                Log.v(TAG, "new notify threadi!");
+                            }
                             mUpdateNotificationThread = new NotificationUpdateThread();
                             mUpdateNotificationThread.start();
-                            if (V) Log.v(TAG, "send delay message");
+                            if (V) {
+                                Log.v(TAG, "send delay message");
+                            }
                             mHandler.sendMessageDelayed(mHandler.obtainMessage(NOTIFY), 1000);
                         } else if (mPendingUpdate > 0) {
-                            if (V) Log.v(TAG, "previous thread is not finished yet");
+                            if (V) {
+                                Log.v(TAG, "previous thread is not finished yet");
+                            }
                             mHandler.sendMessageDelayed(mHandler.obtainMessage(NOTIFY), 1000);
                         }
                         break;
                     }
-              }
-         }
+            }
+        }
     };
 
     private class NotificationUpdateThread extends Thread {
 
-        public NotificationUpdateThread() {
+        NotificationUpdateThread() {
             super("Notification Update Thread");
         }
 
@@ -226,8 +243,9 @@
 
     private void updateActiveNotification() {
         // Active transfers
-        Cursor cursor = mContentResolver.query(
-                BluetoothShare.CONTENT_URI, null, WHERE_RUNNING, null, BluetoothShare._ID);
+        Cursor cursor =
+                mContentResolver.query(BluetoothShare.CONTENT_URI, null, WHERE_RUNNING, null,
+                        BluetoothShare._ID);
         if (cursor == null) {
             return;
         }
@@ -239,7 +257,9 @@
         } else {
             mUpdateCompleteNotification = true;
         }
-        if (V) Log.v(TAG, "mUpdateCompleteNotification = " + mUpdateCompleteNotification);
+        if (V) {
+            Log.v(TAG, "mUpdateCompleteNotification = " + mUpdateCompleteNotification);
+        }
 
         // Collate the notifications
         final int timestampIndex = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP);
@@ -284,10 +304,12 @@
                 if (item.direction == BluetoothShare.DIRECTION_OUTBOUND) {
                     item.description = mContext.getString(R.string.notification_sending, fileName);
                 } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) {
-                    item.description = mContext
-                            .getString(R.string.notification_receiving, fileName);
+                    item.description =
+                            mContext.getString(R.string.notification_receiving, fileName);
                 } else {
-                    if (V) Log.v(TAG, "mDirection ERROR!");
+                    if (V) {
+                        Log.v(TAG, "mDirection ERROR!");
+                    }
                 }
                 item.totalCurrent = current;
                 item.totalTotal = total;
@@ -296,8 +318,10 @@
                 item.destination = destination;
                 mNotifications.put(batchID, item);
 
-                if (V) Log.v(TAG, "ID=" + item.id + "; batchID=" + batchID + "; totoalCurrent"
+                if (V) {
+                    Log.v(TAG, "ID=" + item.id + "; batchID=" + batchID + "; totoalCurrent"
                             + item.totalCurrent + "; totalTotal=" + item.totalTotal);
+                }
             }
         }
         cursor.close();
@@ -309,7 +333,7 @@
                 if (item.totalTotal == -1) {
                     progress = -1;
                 } else {
-                    progress = (float)item.totalCurrent / item.totalTotal;
+                    progress = (float) item.totalCurrent / item.totalTotal;
                 }
 
                 // Let NFC service deal with notifications for this transfer
@@ -331,18 +355,20 @@
             // TODO: split description into two rows with filename in second row
             Notification.Builder b = new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL);
             b.setOnlyAlertOnce(true);
-            b.setColor(mContext.getResources().getColor(
-                    com.android.internal.R.color.system_notification_accent_color,
-                    mContext.getTheme()));
+            b.setColor(mContext.getResources()
+                    .getColor(com.android.internal.R.color.system_notification_accent_color,
+                            mContext.getTheme()));
             b.setContentTitle(item.description);
             b.setSubText(
                     BluetoothOppUtility.formatProgressText(item.totalTotal, item.totalCurrent));
             if (item.totalTotal != 0) {
-                if (V) Log.v(TAG, "mCurrentBytes: " + item.totalCurrent +
-                    " mTotalBytes: " + item.totalTotal + " (" +
-                    (int)((item.totalCurrent * 100) / item.totalTotal) + " %)");
-                b.setProgress(100, (int)((item.totalCurrent * 100) / item.totalTotal),
-                    item.totalTotal == -1);
+                if (V) {
+                    Log.v(TAG, "mCurrentBytes: " + item.totalCurrent + " mTotalBytes: "
+                            + item.totalTotal + " (" + (int) ((item.totalCurrent * 100)
+                            / item.totalTotal) + " %)");
+                }
+                b.setProgress(100, (int) ((item.totalCurrent * 100) / item.totalTotal),
+                        item.totalTotal == -1);
             } else {
                 b.setProgress(100, 100, item.totalTotal == -1);
             }
@@ -352,9 +378,12 @@
             } else if (item.direction == BluetoothShare.DIRECTION_INBOUND) {
                 b.setSmallIcon(android.R.drawable.stat_sys_download);
             } else {
-                if (V) Log.v(TAG, "mDirection ERROR!");
+                if (V) {
+                    Log.v(TAG, "mDirection ERROR!");
+                }
             }
             b.setOngoing(true);
+            b.setLocalOnly(true);
 
             Intent intent = new Intent(Constants.ACTION_LIST);
             intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
@@ -375,8 +404,9 @@
         int inboundFailNumber = 0;
 
         // Creating outbound notification
-        Cursor cursor = mContentResolver.query(BluetoothShare.CONTENT_URI, null,
-                WHERE_COMPLETED_OUTBOUND, null, BluetoothShare.TIMESTAMP + " DESC");
+        Cursor cursor =
+                mContentResolver.query(BluetoothShare.CONTENT_URI, null, WHERE_COMPLETED_OUTBOUND,
+                        null, BluetoothShare.TIMESTAMP + " DESC");
         if (cursor == null) {
             return;
         }
@@ -397,43 +427,49 @@
                 outboundSuccNumber++;
             }
         }
-        if (V) Log.v(TAG, "outbound: succ-" + outboundSuccNumber + "  fail-" + outboundFailNumber);
+        if (V) {
+            Log.v(TAG, "outbound: succ-" + outboundSuccNumber + "  fail-" + outboundFailNumber);
+        }
         cursor.close();
 
         outboundNum = outboundSuccNumber + outboundFailNumber;
         // create the outbound notification
         if (outboundNum > 0) {
-            String unsuccess_caption = mContext.getResources().getQuantityString(
-                    R.plurals.noti_caption_unsuccessful, outboundFailNumber, outboundFailNumber);
-            String caption = mContext.getResources().getQuantityString(
-                    R.plurals.noti_caption_success, outboundSuccNumber, outboundSuccNumber,
-                    unsuccess_caption);
-            Intent content_intent = new Intent(Constants.ACTION_OPEN_OUTBOUND_TRANSFER)
-                    .setClassName(Constants.THIS_PACKAGE_NAME,
-                            BluetoothOppReceiver.class.getName());
-            Intent delete_intent = new Intent(Constants.ACTION_COMPLETE_HIDE)
-                    .setClassName(Constants.THIS_PACKAGE_NAME,
-                            BluetoothOppReceiver.class.getName());
+            String unsuccessCaption = mContext.getResources()
+                    .getQuantityString(R.plurals.noti_caption_unsuccessful, outboundFailNumber,
+                            outboundFailNumber);
+            String caption = mContext.getResources()
+                    .getQuantityString(R.plurals.noti_caption_success, outboundSuccNumber,
+                            outboundSuccNumber, unsuccessCaption);
+            Intent contentIntent = new Intent(Constants.ACTION_OPEN_OUTBOUND_TRANSFER).setClassName(
+                    Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
+            Intent deleteIntent = new Intent(Constants.ACTION_COMPLETE_HIDE).setClassName(
+                    Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
             Notification outNoti =
-                    new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL)
-                            .setOnlyAlertOnce(true)
+                    new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL).setOnlyAlertOnce(
+                            true)
                             .setContentTitle(mContext.getString(R.string.outbound_noti_title))
                             .setContentText(caption)
                             .setSmallIcon(android.R.drawable.stat_sys_upload_done)
-                            .setColor(mContext.getResources().getColor(
-                                    com.android.internal.R.color.system_notification_accent_color,
-                                    mContext.getTheme()))
+                            .setColor(mContext.getResources()
+                                    .getColor(
+                                            com.android.internal.R.color
+                                                    .system_notification_accent_color,
+                                            mContext.getTheme()))
                             .setContentIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, content_intent, 0))
+                                    PendingIntent.getBroadcast(mContext, 0, contentIntent, 0))
                             .setDeleteIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, delete_intent, 0))
+                                    PendingIntent.getBroadcast(mContext, 0, deleteIntent, 0))
                             .setWhen(timeStamp)
+                            .setLocalOnly(true)
                             .build();
             mNotificationMgr.notify(NOTIFICATION_ID_OUTBOUND_COMPLETE, outNoti);
         } else {
             if (mNotificationMgr != null) {
                 mNotificationMgr.cancel(NOTIFICATION_ID_OUTBOUND_COMPLETE);
-                if (V) Log.v(TAG, "outbound notification was removed.");
+                if (V) {
+                    Log.v(TAG, "outbound notification was removed.");
+                }
             }
         }
 
@@ -457,105 +493,116 @@
                 inboundSuccNumber++;
             }
         }
-        if (V) Log.v(TAG, "inbound: succ-" + inboundSuccNumber + "  fail-" + inboundFailNumber);
+        if (V) {
+            Log.v(TAG, "inbound: succ-" + inboundSuccNumber + "  fail-" + inboundFailNumber);
+        }
         cursor.close();
 
         inboundNum = inboundSuccNumber + inboundFailNumber;
         // create the inbound notification
         if (inboundNum > 0) {
-            String unsuccess_caption = mContext.getResources().getQuantityString(
-                    R.plurals.noti_caption_unsuccessful, inboundFailNumber, inboundFailNumber);
-            String caption = mContext.getResources().getQuantityString(
-                    R.plurals.noti_caption_success, inboundSuccNumber, inboundSuccNumber,
-                    unsuccess_caption);
-            Intent content_intent = new Intent(Constants.ACTION_OPEN_INBOUND_TRANSFER)
-                    .setClassName(Constants.THIS_PACKAGE_NAME,
-                            BluetoothOppReceiver.class.getName());
-            Intent delete_intent = new Intent(Constants.ACTION_COMPLETE_HIDE)
-                    .setClassName(Constants.THIS_PACKAGE_NAME,
-                            BluetoothOppReceiver.class.getName());
+            String unsuccessCaption = mContext.getResources()
+                    .getQuantityString(R.plurals.noti_caption_unsuccessful, inboundFailNumber,
+                            inboundFailNumber);
+            String caption = mContext.getResources()
+                    .getQuantityString(R.plurals.noti_caption_success, inboundSuccNumber,
+                            inboundSuccNumber, unsuccessCaption);
+            Intent contentIntent = new Intent(Constants.ACTION_OPEN_INBOUND_TRANSFER).setClassName(
+                    Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
+            Intent deleteIntent = new Intent(Constants.ACTION_COMPLETE_HIDE).setClassName(
+                    Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
             Notification inNoti =
-                    new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL)
-                            .setOnlyAlertOnce(true)
+                    new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL).setOnlyAlertOnce(
+                            true)
                             .setContentTitle(mContext.getString(R.string.inbound_noti_title))
                             .setContentText(caption)
                             .setSmallIcon(android.R.drawable.stat_sys_download_done)
-                            .setColor(mContext.getResources().getColor(
-                                    com.android.internal.R.color.system_notification_accent_color,
-                                    mContext.getTheme()))
+                            .setColor(mContext.getResources()
+                                    .getColor(
+                                            com.android.internal.R.color
+                                                    .system_notification_accent_color,
+                                            mContext.getTheme()))
                             .setContentIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, content_intent, 0))
+                                    PendingIntent.getBroadcast(mContext, 0, contentIntent, 0))
                             .setDeleteIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, delete_intent, 0))
+                                    PendingIntent.getBroadcast(mContext, 0, deleteIntent, 0))
                             .setWhen(timeStamp)
+                            .setLocalOnly(true)
                             .build();
             mNotificationMgr.notify(NOTIFICATION_ID_INBOUND_COMPLETE, inNoti);
         } else {
             if (mNotificationMgr != null) {
                 mNotificationMgr.cancel(NOTIFICATION_ID_INBOUND_COMPLETE);
-                if (V) Log.v(TAG, "inbound notification was removed.");
+                if (V) {
+                    Log.v(TAG, "inbound notification was removed.");
+                }
             }
         }
     }
 
     private void updateIncomingFileConfirmNotification() {
-        Cursor cursor = mContentResolver.query(
-                BluetoothShare.CONTENT_URI, null, WHERE_CONFIRM_PENDING, null, BluetoothShare._ID);
+        Cursor cursor =
+                mContentResolver.query(BluetoothShare.CONTENT_URI, null, WHERE_CONFIRM_PENDING,
+                        null, BluetoothShare._ID);
 
         if (cursor == null) {
             return;
         }
 
         for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
-          BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
-          BluetoothOppUtility.fillRecord(mContext, cursor, info);
-          Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mID);
-          Intent baseIntent = new Intent().setDataAndNormalize(contentUri)
-              .setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
-          Notification.Action actionDecline =
-                  new Notification.Action
-                          .Builder(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,
-                                  mContext.getText(R.string.incoming_file_confirm_ok),
-                                  PendingIntent.getBroadcast(mContext, 0,
-                                          new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT),
-                                          0))
-                          .build();
-          Notification n =
-                  new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL)
-                          .setOnlyAlertOnce(true)
-                          .setOngoing(true)
-                          .setWhen(info.mTimeStamp)
-                          .addAction(actionDecline)
-                          .addAction(actionAccept)
-                          .setContentIntent(PendingIntent.getBroadcast(mContext, 0,
-                                  new Intent(baseIntent)
-                                          .setAction(Constants.ACTION_INCOMING_FILE_CONFIRM),
-                                  0))
-                          .setDeleteIntent(PendingIntent.getBroadcast(mContext, 0,
-                                  new Intent(baseIntent).setAction(Constants.ACTION_HIDE), 0))
-                          .setColor(mContext.getResources().getColor(
-                                  com.android.internal.R.color.system_notification_accent_color,
-                                  mContext.getTheme()))
-                          .setContentTitle(mContext.getText(
-                                  R.string.incoming_file_confirm_Notification_title))
-                          .setContentText(info.mFileName)
-                          .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
-                                  R.string.incoming_file_confirm_Notification_content,
-                                  info.mDeviceName, info.mFileName)))
-                          .setContentInfo(Formatter.formatFileSize(mContext, info.mTotalBytes))
-                          .setSmallIcon(R.drawable.bt_incomming_file_notification)
-                          .build();
-          mNotificationMgr.notify(NOTIFICATION_ID_PROGRESS, n);
+            BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
+            BluetoothOppUtility.fillRecord(mContext, cursor, info);
+            Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mID);
+            Intent baseIntent = new Intent().setDataAndNormalize(contentUri)
+                    .setClassName(Constants.THIS_PACKAGE_NAME,
+                            BluetoothOppReceiver.class.getName());
+            Notification.Action actionDecline =
+                    new Notification.Action.Builder(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,
+                    mContext.getText(R.string.incoming_file_confirm_ok),
+                    PendingIntent.getBroadcast(mContext, 0,
+                            new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT), 0)).build();
+            Notification n =
+                    new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL).setOnlyAlertOnce(
+                            true)
+                            .setOngoing(true)
+                            .setWhen(info.mTimeStamp)
+                            .addAction(actionDecline)
+                            .addAction(actionAccept)
+                            .setContentIntent(PendingIntent.getBroadcast(mContext, 0,
+                                    new Intent(baseIntent).setAction(
+                                            Constants.ACTION_INCOMING_FILE_CONFIRM), 0))
+                            .setDeleteIntent(PendingIntent.getBroadcast(mContext, 0,
+                                    new Intent(baseIntent).setAction(Constants.ACTION_HIDE), 0))
+                            .setColor(mContext.getResources()
+                                    .getColor(
+                                            com.android.internal.R.color
+                                                    .system_notification_accent_color,
+                                            mContext.getTheme()))
+                            .setContentTitle(mContext.getText(
+                                    R.string.incoming_file_confirm_Notification_title))
+                            .setContentText(info.mFileName)
+                            .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
+                                    R.string.incoming_file_confirm_Notification_content,
+                                    info.mDeviceName, info.mFileName)))
+                            .setContentInfo(Formatter.formatFileSize(mContext, info.mTotalBytes))
+                            .setSmallIcon(R.drawable.bt_incomming_file_notification)
+                            .setLocalOnly(true)
+                            .build();
+            mNotificationMgr.notify(NOTIFICATION_ID_PROGRESS, n);
         }
         cursor.close();
     }
+
+    void cancelNotifications() {
+        if (V) {
+            Log.v(TAG, "cancelNotifications ");
+        }
+        mHandler.removeCallbacksAndMessages(null);
+        mNotificationMgr.cancelAll();
+    }
 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
index 2697834..1d01646 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java
@@ -32,13 +32,6 @@
 
 package com.android.bluetooth.opp;
 
-import javax.obex.ClientOperation;
-import javax.obex.ClientSession;
-import javax.obex.HeaderSet;
-import javax.obex.ObexTransport;
-import javax.obex.ResponseCodes;
-
-import android.app.NotificationManager;
 import android.content.ContentValues;
 import android.content.Context;
 import android.net.Uri;
@@ -47,13 +40,22 @@
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.Process;
+import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.btservice.MetricsLogger;
+
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.lang.Thread;
+
+import javax.obex.ClientOperation;
+import javax.obex.ClientSession;
+import javax.obex.HeaderSet;
+import javax.obex.ObexTransport;
+import javax.obex.ResponseCodes;
 
 /**
  * This class runs as an OBEX client
@@ -76,6 +78,8 @@
 
     private Handler mCallback;
 
+    private int mNumFilesAttemptedToSend;
+
     public BluetoothOppObexClientSession(Context context, ObexTransport transport) {
         if (transport == null) {
             throw new NullPointerException("transport is null");
@@ -84,33 +88,41 @@
         mTransport = transport;
     }
 
+    @Override
     public void start(Handler handler, int numShares) {
-        if (D) Log.d(TAG, "Start!");
+        if (D) {
+            Log.d(TAG, "Start!");
+        }
         mCallback = handler;
         mThread = new ClientThread(mContext, mTransport, numShares);
         mThread.start();
     }
 
+    @Override
     public void stop() {
-        if (D) Log.d(TAG, "Stop!");
+        if (D) {
+            Log.d(TAG, "Stop!");
+        }
         if (mThread != null) {
             mInterrupted = true;
             try {
                 mThread.interrupt();
-                if (V) Log.v(TAG, "waiting for thread to terminate");
+                if (V) {
+                    Log.v(TAG, "waiting for thread to terminate");
+                }
                 mThread.join();
                 mThread = null;
             } catch (InterruptedException e) {
-                if (V) Log.v(TAG, "Interrupted waiting for thread to join");
+                if (V) {
+                    Log.v(TAG, "Interrupted waiting for thread to join");
+                }
             }
         }
-        NotificationManager nm =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        nm.cancel(BluetoothOppNotification.NOTIFICATION_ID_PROGRESS);
-
+        BluetoothOppUtility.cancelNotification(mContext);
         mCallback = null;
     }
 
+    @Override
     public void addShare(BluetoothOppShareInfo share) {
         mThread.addShare(share);
     }
@@ -119,7 +131,9 @@
         int done = 0;
         while (done < size) {
             int got = is.read(buffer, done, size - done);
-            if (got <= 0) break;
+            if (got <= 0) {
+                break;
+            }
             done += got;
         }
         return done;
@@ -127,19 +141,19 @@
 
     private class ClientThread extends Thread {
 
-        private static final int sSleepTime = 500;
+        private static final int SLEEP_TIME = 500;
 
         private Context mContext1;
 
         private BluetoothOppShareInfo mInfo;
 
-        private volatile boolean waitingForShare;
+        private volatile boolean mWaitingForShare;
 
         private ObexTransport mTransport1;
 
         private ClientSession mCs;
 
-        private WakeLock wakeLock;
+        private WakeLock mWakeLock;
 
         private BluetoothOppSendFileInfo mFileInfo = null;
 
@@ -147,48 +161,55 @@
 
         private int mNumShares;
 
-        public ClientThread(Context context, ObexTransport transport, int initialNumShares) {
+        ClientThread(Context context, ObexTransport transport, int initialNumShares) {
             super("BtOpp ClientThread");
             mContext1 = context;
             mTransport1 = transport;
-            waitingForShare = true;
+            mWaitingForShare = true;
             mWaitingForRemote = false;
             mNumShares = initialNumShares;
-            PowerManager pm = (PowerManager)mContext1.getSystemService(Context.POWER_SERVICE);
-            wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+            PowerManager pm = (PowerManager) mContext1.getSystemService(Context.POWER_SERVICE);
+            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         }
 
         public void addShare(BluetoothOppShareInfo info) {
             mInfo = info;
             mFileInfo = processShareInfo();
-            waitingForShare = false;
+            mWaitingForShare = false;
         }
 
         @Override
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 
-            if (V) Log.v(TAG, "acquire partial WakeLock");
-            wakeLock.acquire();
+            if (V) {
+                Log.v(TAG, "acquire partial WakeLock");
+            }
+            mWakeLock.acquire();
 
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e1) {
-                if (V) Log.v(TAG, "Client thread was interrupted (1), exiting");
+                if (V) {
+                    Log.v(TAG, "Client thread was interrupted (1), exiting");
+                }
                 mInterrupted = true;
             }
             if (!mInterrupted) {
                 connect(mNumShares);
             }
 
+            mNumFilesAttemptedToSend = 0;
             while (!mInterrupted) {
-                if (!waitingForShare) {
+                if (!mWaitingForShare) {
                     doSend();
                 } else {
                     try {
-                        if (D) Log.d(TAG, "Client thread waiting for next share, sleep for "
-                                    + sSleepTime);
-                        Thread.sleep(sSleepTime);
+                        if (D) {
+                            Log.d(TAG, "Client thread waiting for next share, sleep for "
+                                    + SLEEP_TIME);
+                        }
+                        Thread.sleep(SLEEP_TIME);
                     } catch (InterruptedException e) {
 
                     }
@@ -196,9 +217,16 @@
             }
             disconnect();
 
-            if (wakeLock.isHeld()) {
-                if (V) Log.v(TAG, "release partial WakeLock");
-                wakeLock.release();
+            if (mWakeLock.isHeld()) {
+                if (V) {
+                    Log.v(TAG, "release partial WakeLock");
+                }
+                mWakeLock.release();
+            }
+
+            if (mNumFilesAttemptedToSend > 0) {
+                // Log outgoing OPP transfer if more than one file is accepted by remote
+                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.OPP);
             }
             Message msg = Message.obtain(mCallback);
             msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE;
@@ -213,16 +241,22 @@
                     mCs.disconnect(null);
                 }
                 mCs = null;
-                if (D) Log.d(TAG, "OBEX session disconnected");
+                if (D) {
+                    Log.d(TAG, "OBEX session disconnected");
+                }
             } catch (IOException e) {
                 Log.w(TAG, "OBEX session disconnect error" + e);
             }
             try {
                 if (mCs != null) {
-                    if (D) Log.d(TAG, "OBEX session close mCs");
-                    mCs.close();
-                    if (D) Log.d(TAG, "OBEX session closed");
+                    if (D) {
+                        Log.d(TAG, "OBEX session close mCs");
                     }
+                    mCs.close();
+                    if (D) {
+                        Log.d(TAG, "OBEX session closed");
+                    }
+                }
             } catch (IOException e) {
                 Log.w(TAG, "OBEX session close error" + e);
             }
@@ -237,7 +271,9 @@
         }
 
         private void connect(int numShares) {
-            if (D) Log.d(TAG, "Create ClientSession with transport " + mTransport1.toString());
+            if (D) {
+                Log.d(TAG, "Create ClientSession with transport " + mTransport1.toString());
+            }
             try {
                 mCs = new ClientSession(mTransport1);
                 mConnected = true;
@@ -253,7 +289,9 @@
                 }
                 try {
                     mCs.connect(hs);
-                    if (D) Log.d(TAG, "OBEX session created");
+                    if (D) {
+                        Log.d(TAG, "OBEX session created");
+                    }
                     mConnected = true;
                 } catch (IOException e) {
                     Log.e(TAG, "OBEX session connect error");
@@ -288,15 +326,15 @@
                     /* this is invalid request */
                     status = mFileInfo.mStatus;
                 }
-                waitingForShare = true;
+                mWaitingForShare = true;
             } else {
                 Constants.updateShareStatus(mContext1, mInfo.mId, status);
             }
 
             Message msg = Message.obtain(mCallback);
-            msg.what = (status == BluetoothShare.STATUS_SUCCESS) ?
-                        BluetoothOppObexSession.MSG_SHARE_COMPLETE :
-                        BluetoothOppObexSession.MSG_SESSION_ERROR;
+            msg.what = (status == BluetoothShare.STATUS_SUCCESS)
+                    ? BluetoothOppObexSession.MSG_SHARE_COMPLETE
+                    : BluetoothOppObexSession.MSG_SESSION_ERROR;
             mInfo.mStatus = status;
             msg.obj = mInfo;
             msg.sendToTarget();
@@ -306,12 +344,16 @@
          * Validate this ShareInfo
          */
         private BluetoothOppSendFileInfo processShareInfo() {
-            if (V) Log.v(TAG, "Client thread processShareInfo() " + mInfo.mId);
+            if (V) {
+                Log.v(TAG, "Client thread processShareInfo() " + mInfo.mId);
+            }
 
             BluetoothOppSendFileInfo fileInfo = BluetoothOppUtility.getSendFileInfo(mInfo.mUri);
             if (fileInfo.mFileName == null || fileInfo.mLength == 0) {
-                if (V) Log.v(TAG, "BluetoothOppSendFileInfo get invalid file");
-                    Constants.updateShareStatus(mContext1, mInfo.mId, fileInfo.mStatus);
+                if (V) {
+                    Log.v(TAG, "BluetoothOppSendFileInfo get invalid file");
+                }
+                Constants.updateShareStatus(mContext1, mInfo.mId, fileInfo.mStatus);
 
             } else {
                 if (V) {
@@ -341,16 +383,7 @@
             int status = BluetoothShare.STATUS_SUCCESS;
             Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId);
             ContentValues updateValues;
-            HeaderSet request;
-            request = new HeaderSet();
-            request.setHeader(HeaderSet.NAME, fileInfo.mFileName);
-            request.setHeader(HeaderSet.TYPE, fileInfo.mMimetype);
-
-            applyRemoteDeviceQuirks(request, mInfo.mDestination, fileInfo.mFileName);
-
-            Constants.updateShareStatus(mContext1, mInfo.mId, BluetoothShare.STATUS_RUNNING);
-
-            request.setHeader(HeaderSet.LENGTH, fileInfo.mLength);
+            HeaderSet request = new HeaderSet();
             ClientOperation putOperation = null;
             OutputStream outputStream = null;
             InputStream inputStream = null;
@@ -359,8 +392,28 @@
                     mWaitingForRemote = true;
                 }
                 try {
-                    if (V) Log.v(TAG, "put headerset for " + fileInfo.mFileName);
-                    putOperation = (ClientOperation)mCs.put(request);
+                    if (V) {
+                        Log.v(TAG, "Set header items for " + fileInfo.mFileName);
+                    }
+                    request.setHeader(HeaderSet.NAME, fileInfo.mFileName);
+                    request.setHeader(HeaderSet.TYPE, fileInfo.mMimetype);
+
+                    applyRemoteDeviceQuirks(request, mInfo.mDestination, fileInfo.mFileName);
+                    Constants.updateShareStatus(mContext1, mInfo.mId,
+                            BluetoothShare.STATUS_RUNNING);
+
+                    request.setHeader(HeaderSet.LENGTH, fileInfo.mLength);
+
+                    if (V) {
+                        Log.v(TAG, "put headerset for " + fileInfo.mFileName);
+                    }
+                    putOperation = (ClientOperation) mCs.put(request);
+                } catch (IllegalArgumentException e) {
+                    status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
+                    Constants.updateShareStatus(mContext1, mInfo.mId, status);
+
+                    Log.e(TAG, "Error setting header items for request: " + e);
+                    error = true;
                 } catch (IOException e) {
                     status = BluetoothShare.STATUS_OBEX_DATA_ERROR;
                     Constants.updateShareStatus(mContext1, mInfo.mId, status);
@@ -374,7 +427,9 @@
 
                 if (!error) {
                     try {
-                        if (V) Log.v(TAG, "openOutputStream " + fileInfo.mFileName);
+                        if (V) {
+                            Log.v(TAG, "openOutputStream " + fileInfo.mFileName);
+                        }
                         outputStream = putOperation.openOutputStream();
                         inputStream = putOperation.openInputStream();
                     } catch (IOException e) {
@@ -397,6 +452,8 @@
                     long prevPercent = 0;
                     boolean okToProceed = false;
                     long timestamp = 0;
+                    long currentTime = 0;
+                    long prevTimestamp = SystemClock.elapsedRealtime();
                     int outputBufferSize = putOperation.getMaxPacketSize();
                     byte[] buffer = new byte[outputBufferSize];
                     BufferedInputStream a = new BufferedInputStream(fileInfo.mInputStream, 0x4000);
@@ -404,8 +461,8 @@
                     if (!mInterrupted && (position != fileInfo.mLength)) {
                         readLength = readFully(a, buffer, outputBufferSize);
 
-                        mCallback.sendMessageDelayed(mCallback
-                                .obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
+                        mCallback.sendMessageDelayed(mCallback.obtainMessage(
+                                BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
                                 BluetoothOppObexSession.SESSION_TIMEOUT);
                         synchronized (this) {
                             mWaitingForRemote = true;
@@ -433,45 +490,56 @@
 
                         if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE
                                 || responseCode == ResponseCodes.OBEX_HTTP_OK) {
-                            if (V) Log.v(TAG, "Remote accept");
+                            if (V) {
+                                Log.v(TAG, "Remote accept");
+                            }
                             okToProceed = true;
                             updateValues = new ContentValues();
                             updateValues.put(BluetoothShare.CURRENT_BYTES, position);
-                            mContext1.getContentResolver().update(contentUri, updateValues, null,
-                                    null);
+                            mContext1.getContentResolver()
+                                    .update(contentUri, updateValues, null, null);
+                            mNumFilesAttemptedToSend++;
                         } else {
                             Log.i(TAG, "Remote reject, Response code is " + responseCode);
                         }
                     }
 
                     while (!mInterrupted && okToProceed && (position < fileInfo.mLength)) {
-                        if (V) timestamp = System.currentTimeMillis();
+                        if (V) {
+                            timestamp = SystemClock.elapsedRealtime();
+                        }
 
                         readLength = a.read(buffer, 0, outputBufferSize);
                         outputStream.write(buffer, 0, readLength);
 
                         /* check remote abort */
                         responseCode = putOperation.getResponseCode();
-                        if (V) Log.v(TAG, "Response code is " + responseCode);
+                        if (V) {
+                            Log.v(TAG, "Response code is " + responseCode);
+                        }
                         if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE
                                 && responseCode != ResponseCodes.OBEX_HTTP_OK) {
                             /* abort happens */
                             okToProceed = false;
                         } else {
                             position += readLength;
+                            currentTime = SystemClock.elapsedRealtime();
                             if (V) {
                                 Log.v(TAG, "Sending file position = " + position
                                         + " readLength " + readLength + " bytes took "
-                                        + (System.currentTimeMillis() - timestamp) + " ms");
+                                        + (currentTime - timestamp) + " ms");
                             }
                             // Update the Progress Bar only if there is change in percentage
+                            // or once per a period to notify NFC of this transfer is still alive
                             percent = position * 100 / fileInfo.mLength;
-                            if (percent > prevPercent) {
+                            if (percent > prevPercent
+                                    || currentTime - prevTimestamp > Constants.NFC_ALIVE_CHECK_MS) {
                                 updateValues = new ContentValues();
                                 updateValues.put(BluetoothShare.CURRENT_BYTES, position);
-                                mContext1.getContentResolver().update(contentUri, updateValues,
-                                        null, null);
+                                mContext1.getContentResolver()
+                                        .update(contentUri, updateValues, null, null);
                                 prevPercent = percent;
+                                prevTimestamp = currentTime;
                             }
                         }
                     }
@@ -485,8 +553,9 @@
                         Log.i(TAG, "Remote reject file type " + fileInfo.mMimetype);
                         status = BluetoothShare.STATUS_NOT_ACCEPTABLE;
                     } else if (!mInterrupted && position == fileInfo.mLength) {
-                        Log.i(TAG, "SendFile finished send out file " + fileInfo.mFileName
-                                + " length " + fileInfo.mLength);
+                        Log.i(TAG,
+                                "SendFile finished send out file " + fileInfo.mFileName + " length "
+                                        + fileInfo.mLength);
                     } else {
                         error = true;
                         status = BluetoothShare.STATUS_CANCELED;
@@ -507,13 +576,19 @@
                     if (outputStream != null) {
                         outputStream.close();
                     }
+                } catch (IOException e) {
+                    Log.e(TAG, "Error when closing output stream after send");
+                }
 
-                    // Close InputStream and remove SendFileInfo from map
-                    BluetoothOppUtility.closeSendFileInfo(mInfo.mUri);
+                // Close InputStream and remove SendFileInfo from map
+                BluetoothOppUtility.closeSendFileInfo(mInfo.mUri);
+                try {
                     if (!error) {
                         responseCode = putOperation.getResponseCode();
                         if (responseCode != -1) {
-                            if (V) Log.v(TAG, "Get response code " + responseCode);
+                            if (V) {
+                                Log.v(TAG, "Get response code " + responseCode);
+                            }
                             if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
                                 Log.i(TAG, "Response error code is " + responseCode);
                                 status = BluetoothShare.STATUS_UNHANDLED_OBEX_CODE;
@@ -545,11 +620,12 @@
                     // Socket has been closed due to the response timeout in the framework,
                     // mark the transfer as failure.
                     if (position != fileInfo.mLength) {
-                       status = BluetoothShare.STATUS_FORBIDDEN;
-                       Constants.updateShareStatus(mContext1, mInfo.mId, status);
+                        status = BluetoothShare.STATUS_FORBIDDEN;
+                        Constants.updateShareStatus(mContext1, mInfo.mId, status);
                     }
                 }
             }
+            BluetoothOppUtility.cancelNotification(mContext);
             return status;
         }
 
@@ -558,7 +634,7 @@
             // Update interrupted outbound content resolver entry when
             // error during transfer.
             Constants.updateShareStatus(mContext1, mInfo.mId,
-                BluetoothShare.STATUS_OBEX_DATA_ERROR);
+                    BluetoothShare.STATUS_OBEX_DATA_ERROR);
             mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
         }
 
@@ -567,7 +643,9 @@
             super.interrupt();
             synchronized (this) {
                 if (mWaitingForRemote) {
-                    if (V) Log.v(TAG, "Interrupted when waitingForRemote");
+                    if (V) {
+                        Log.v(TAG, "Interrupted when waitingForRemote");
+                    }
                     try {
                         mTransport1.close();
                     } catch (IOException e) {
@@ -609,12 +687,13 @@
             if (modified) {
                 String newFilename = new String(c);
                 request.setHeader(HeaderSet.NAME, newFilename);
-                Log.i(TAG, "Sending file \"" + filename + "\" as \"" + newFilename +
-                        "\" to workaround Poloroid filename quirk");
+                Log.i(TAG, "Sending file \"" + filename + "\" as \"" + newFilename
+                        + "\" to workaround Poloroid filename quirk");
             }
         }
     }
 
+    @Override
     public void unblock() {
         // Not used for client case
     }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
index 5b4cddc..47e80c8 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
@@ -32,13 +32,6 @@
 
 package com.android.bluetooth.opp;
 
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-
-import android.app.NotificationManager;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -47,9 +40,20 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
 import android.util.Log;
 import android.webkit.MimeTypeMap;
 
+import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothObexTransport;
+import com.android.bluetooth.btservice.MetricsLogger;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
 import javax.obex.HeaderSet;
 import javax.obex.ObexTransport;
 import javax.obex.Operation;
@@ -57,14 +61,11 @@
 import javax.obex.ServerRequestHandler;
 import javax.obex.ServerSession;
 
-import com.android.bluetooth.BluetoothObexTransport;
-import com.android.bluetooth.ObexServerSockets;
-
 /**
  * This class runs as an OBEX server
  */
-public class BluetoothOppObexServerSession extends ServerRequestHandler implements
-        BluetoothOppObexSession {
+public class BluetoothOppObexServerSession extends ServerRequestHandler
+        implements BluetoothOppObexSession {
 
     private static final String TAG = "BtOppObexServer";
     private static final boolean D = Constants.DEBUG;
@@ -99,18 +100,21 @@
 
     boolean mTimeoutMsgSent = false;
 
-    private ObexServerSockets mServerSocket;
+    private BluetoothOppService mBluetoothOppService;
 
-    public BluetoothOppObexServerSession(
-            Context context, ObexTransport transport, ObexServerSockets serverSocket) {
+    private int mNumFilesAttemptedToReceive;
+
+    public BluetoothOppObexServerSession(Context context, ObexTransport transport,
+            BluetoothOppService service) {
         mContext = context;
         mTransport = transport;
-        mServerSocket = serverSocket;
-        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        mBluetoothOppService = service;
+        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mPartialWakeLock.setReferenceCounted(false);
     }
 
+    @Override
     public void unblock() {
         mServerBlocking = false;
     }
@@ -121,7 +125,9 @@
      */
     public void preStart() {
         try {
-            if (D) Log.d(TAG, "Create ServerSession with transport " + mTransport.toString());
+            if (D) {
+                Log.d(TAG, "Create ServerSession with transport " + mTransport.toString());
+            }
             mSession = new ServerSession(mTransport, this, null);
         } catch (IOException e) {
             Log.e(TAG, "Create server session error" + e);
@@ -131,8 +137,11 @@
     /**
      * Called from BluetoothOppTransfer to start the "Transfer"
      */
+    @Override
     public void start(Handler handler, int numShares) {
-        if (D) Log.d(TAG, "Start!");
+        if (D) {
+            Log.d(TAG, "Start!");
+        }
         mCallback = handler;
 
     }
@@ -141,12 +150,15 @@
      * Called from BluetoothOppTransfer to cancel the "Transfer" Otherwise,
      * server should end by itself.
      */
+    @Override
     public void stop() {
         /*
          * TODO now we implement in a tough way, just close the socket.
          * maybe need nice way
          */
-        if (D) Log.d(TAG, "Stop!");
+        if (D) {
+            Log.d(TAG, "Stop!");
+        }
         mInterrupted = true;
         if (mSession != null) {
             try {
@@ -160,128 +172,113 @@
         mSession = null;
     }
 
+    @Override
     public void addShare(BluetoothOppShareInfo info) {
-        if (D) Log.d(TAG, "addShare for id " + info.mId);
+        if (D) {
+            Log.d(TAG, "addShare for id " + info.mId);
+        }
         mInfo = info;
         mFileInfo = processShareInfo();
     }
 
     @Override
     public int onPut(Operation op) {
-        if (D) Log.d(TAG, "onPut " + op.toString());
-        HeaderSet request;
-        String name, mimeType;
-        Long length;
+        if (D) {
+            Log.d(TAG, "onPut " + op.toString());
+        }
 
-        int obexResponse = ResponseCodes.OBEX_HTTP_OK;
-
-        /**
-         * For multiple objects, reject further objects after user deny the
-         * first one
-         */
+        /* For multiple objects, reject further objects after the user denies the first one */
         if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED) {
             return ResponseCodes.OBEX_HTTP_FORBIDDEN;
         }
 
         String destination;
         if (mTransport instanceof BluetoothObexTransport) {
-            destination = ((BluetoothObexTransport)mTransport).getRemoteAddress();
+            destination = ((BluetoothObexTransport) mTransport).getRemoteAddress();
         } else {
             destination = "FF:FF:FF:00:00:00";
         }
-        boolean isWhitelisted = BluetoothOppManager.getInstance(mContext).
-                isWhitelisted(destination);
+        boolean isWhitelisted =
+                BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
 
+        HeaderSet request;
+        String name, mimeType;
+        Long length;
         try {
-            boolean pre_reject = false;
-
             request = op.getReceivedHeader();
-            if (V) Constants.logHeader(request);
-            name = (String)request.getHeader(HeaderSet.NAME);
-            length = (Long)request.getHeader(HeaderSet.LENGTH);
-            mimeType = (String)request.getHeader(HeaderSet.TYPE);
-
-            if (length == 0) {
-                if (D) Log.w(TAG, "length is 0, reject the transfer");
-                pre_reject = true;
-                obexResponse = ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED;
+            if (V) {
+                Constants.logHeader(request);
             }
-
-            if (name == null || name.equals("")) {
-                if (D) Log.w(TAG, "name is null or empty, reject the transfer");
-                pre_reject = true;
-                obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
-            }
-
-            if (!pre_reject) {
-                /* first we look for Mimetype in Android map */
-                String extension, type;
-                int dotIndex = name.lastIndexOf(".");
-                if (dotIndex < 0 && mimeType == null) {
-                    if (D) Log.w(TAG, "There is no file extension or mime type," +
-                            "reject the transfer");
-                    pre_reject = true;
-                    obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST;
-                } else {
-                    extension = name.substring(dotIndex + 1).toLowerCase();
-                    MimeTypeMap map = MimeTypeMap.getSingleton();
-                    type = map.getMimeTypeFromExtension(extension);
-                    if (V) Log.v(TAG, "Mimetype guessed from extension " + extension + " is " + type);
-                    if (type != null) {
-                        mimeType = type;
-
-                    } else {
-                        if (mimeType == null) {
-                            if (D) Log.w(TAG, "Can't get mimetype, reject the transfer");
-                            pre_reject = true;
-                            obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
-                        }
-                    }
-                    if (mimeType != null) {
-                        mimeType = mimeType.toLowerCase();
-                    }
-                }
-            }
-
-            // Reject policy: anything outside the "white list" plus unspecified
-            // MIME Types. Also reject everything in the "black list".
-            if (!pre_reject
-                    && (mimeType == null
-                            || (!isWhitelisted && !Constants.mimeTypeMatches(mimeType,
-                                    Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))
-                            || Constants.mimeTypeMatches(mimeType,
-                                    Constants.UNACCEPTABLE_SHARE_INBOUND_TYPES))) {
-                if (D) Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer");
-                pre_reject = true;
-                obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
-            }
-
-            if (pre_reject && obexResponse != ResponseCodes.OBEX_HTTP_OK) {
-                // some bad implemented client won't send disconnect
-                return obexResponse;
-            }
-
+            name = (String) request.getHeader(HeaderSet.NAME);
+            length = (Long) request.getHeader(HeaderSet.LENGTH);
+            mimeType = (String) request.getHeader(HeaderSet.TYPE);
         } catch (IOException e) {
-            Log.e(TAG, "get getReceivedHeaders error " + e);
+            Log.e(TAG, "onPut: getReceivedHeaders error " + e);
             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
         }
 
+        if (length == 0) {
+            if (D) {
+                Log.w(TAG, "length is 0, reject the transfer");
+            }
+            return ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED;
+        }
+
+        if (name == null || name.isEmpty()) {
+            if (D) {
+                Log.w(TAG, "name is null or empty, reject the transfer");
+            }
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        // First we look for the mime type in the Android map
+        String extension, type;
+        int dotIndex = name.lastIndexOf(".");
+        if (dotIndex < 0 && mimeType == null) {
+            if (D) {
+                Log.w(TAG, "There is no file extension or mime type, reject the transfer");
+            }
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        } else {
+            extension = name.substring(dotIndex + 1).toLowerCase();
+            MimeTypeMap map = MimeTypeMap.getSingleton();
+            type = map.getMimeTypeFromExtension(extension);
+            if (V) {
+                Log.v(TAG, "Mimetype guessed from extension " + extension + " is " + type);
+            }
+            if (type != null) {
+                mimeType = type;
+            } else {
+                if (mimeType == null) {
+                    if (D) {
+                        Log.w(TAG, "Can't get mimetype, reject the transfer");
+                    }
+                    return ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
+                }
+            }
+            mimeType = mimeType.toLowerCase();
+        }
+
+        // Reject anything outside the "whitelist" plus unspecified MIME Types.
+        if (mimeType == null || (!isWhitelisted && !Constants.mimeTypeMatches(mimeType,
+                Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))) {
+            if (D) {
+                Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer");
+            }
+            return ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE;
+        }
+
         ContentValues values = new ContentValues();
-
         values.put(BluetoothShare.FILENAME_HINT, name);
-
         values.put(BluetoothShare.TOTAL_BYTES, length);
-
         values.put(BluetoothShare.MIMETYPE, mimeType);
-
         values.put(BluetoothShare.DESTINATION, destination);
-
         values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_INBOUND);
         values.put(BluetoothShare.TIMESTAMP, mTimestamp);
 
-        /** It's not first put if !serverBlocking, so we auto accept it */
-        if (!mServerBlocking && (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED ||
-                mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED)) {
+        // It's not first put if !serverBlocking, so we auto accept it
+        if (!mServerBlocking && (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED
+                || mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED)) {
             values.put(BluetoothShare.USER_CONFIRMATION,
                     BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED);
         }
@@ -289,14 +286,15 @@
         if (isWhitelisted) {
             values.put(BluetoothShare.USER_CONFIRMATION,
                     BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
-
         }
 
         Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
         mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1));
 
-        if (V) Log.v(TAG, "insert contentUri: " + contentUri);
-        if (V) Log.v(TAG, "mLocalShareInfoId = " + mLocalShareInfoId);
+        if (V) {
+            Log.v(TAG, "insert contentUri: " + contentUri);
+            Log.v(TAG, "mLocalShareInfoId = " + mLocalShareInfoId);
+        }
 
         synchronized (this) {
             mPartialWakeLock.acquire();
@@ -306,18 +304,24 @@
                 while (mServerBlocking) {
                     wait(1000);
                     if (mCallback != null && !mTimeoutMsgSent) {
-                        mCallback.sendMessageDelayed(mCallback
-                                .obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
+                        mCallback.sendMessageDelayed(mCallback.obtainMessage(
+                                BluetoothOppObexSession.MSG_CONNECT_TIMEOUT),
                                 BluetoothOppObexSession.SESSION_TIMEOUT);
                         mTimeoutMsgSent = true;
-                        if (V) Log.v(TAG, "MSG_CONNECT_TIMEOUT sent");
+                        if (V) {
+                            Log.v(TAG, "MSG_CONNECT_TIMEOUT sent");
+                        }
                     }
                 }
             } catch (InterruptedException e) {
-                if (V) Log.v(TAG, "Interrupted in onPut blocking");
+                if (V) {
+                    Log.v(TAG, "Interrupted in onPut blocking");
+                }
             }
         }
-        if (D) Log.d(TAG, "Server unblocked ");
+        if (D) {
+            Log.d(TAG, "Server unblocked ");
+        }
         synchronized (this) {
             if (mCallback != null && mTimeoutMsgSent) {
                 mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
@@ -335,13 +339,18 @@
         }
         mAccepted = mInfo.mConfirm;
 
-        if (V) Log.v(TAG, "after confirm: userAccepted=" + mAccepted);
+        if (V) {
+            Log.v(TAG, "after confirm: userAccepted=" + mAccepted);
+        }
         int status = BluetoothShare.STATUS_SUCCESS;
 
+        int obexResponse = ResponseCodes.OBEX_HTTP_OK;
+
         if (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED
                 || mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED
                 || mAccepted == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) {
             /* Confirm or auto-confirm */
+            mNumFilesAttemptedToReceive++;
 
             if (mFileInfo.mFileName == null) {
                 status = mFileInfo.mStatus;
@@ -376,8 +385,8 @@
                 msg.sendToTarget();
             } else {
                 if (mCallback != null) {
-                    Message msg = Message.obtain(mCallback,
-                            BluetoothOppObexSession.MSG_SESSION_ERROR);
+                    Message msg =
+                            Message.obtain(mCallback, BluetoothOppObexSession.MSG_SESSION_ERROR);
                     mInfo.mStatus = status;
                     msg.obj = mInfo;
                     msg.sendToTarget();
@@ -442,7 +451,7 @@
         }
 
         long position = 0;
-        long percent = 0;
+        long percent;
         long prevPercent = 0;
 
         if (!error) {
@@ -452,36 +461,46 @@
         if (!error) {
             int outputBufferSize = op.getMaxPacketSize();
             byte[] b = new byte[outputBufferSize];
-            int readLength = 0;
+            int readLength;
             long timestamp = 0;
+            long currentTime;
+            long prevTimestamp = SystemClock.elapsedRealtime();
             try {
                 while ((!mInterrupted) && (position != fileInfo.mLength)) {
 
-                    if (V) timestamp = System.currentTimeMillis();
+                    if (V) {
+                        timestamp = SystemClock.elapsedRealtime();
+                    }
 
                     readLength = is.read(b);
 
                     if (readLength == -1) {
-                        if (D) Log.d(TAG, "Receive file reached stream end at position" + position);
+                        if (D) {
+                            Log.d(TAG, "Receive file reached stream end at position" + position);
+                        }
                         break;
                     }
 
                     bos.write(b, 0, readLength);
                     position += readLength;
                     percent = position * 100 / fileInfo.mLength;
+                    currentTime = SystemClock.elapsedRealtime();
 
                     if (V) {
-                        Log.v(TAG, "Receive file position = " + position + " readLength "
-                                + readLength + " bytes took "
-                                + (System.currentTimeMillis() - timestamp) + " ms");
+                        Log.v(TAG,
+                                "Receive file position = " + position + " readLength " + readLength
+                                        + " bytes took " + (currentTime - timestamp) + " ms");
                     }
 
                     // Update the Progress Bar only if there is change in percentage
-                    if (percent > prevPercent) {
+                    // or once per a period to notify NFC of this transfer is still alive
+                    if (percent > prevPercent
+                            || currentTime - prevTimestamp > Constants.NFC_ALIVE_CHECK_MS) {
                         ContentValues updateValues = new ContentValues();
                         updateValues.put(BluetoothShare.CURRENT_BYTES, position);
                         mContext.getContentResolver().update(contentUri, updateValues, null, null);
                         prevPercent = percent;
+                        prevTimestamp = currentTime;
                     }
                 }
             } catch (IOException e1) {
@@ -497,14 +516,20 @@
         }
 
         if (mInterrupted) {
-            if (D) Log.d(TAG, "receiving file interrupted by user.");
+            if (D) {
+                Log.d(TAG, "receiving file interrupted by user.");
+            }
             status = BluetoothShare.STATUS_CANCELED;
         } else {
             if (position == fileInfo.mLength) {
-                if (D) Log.d(TAG, "Receiving file completed for " + fileInfo.mFileName);
+                if (D) {
+                    Log.d(TAG, "Receiving file completed for " + fileInfo.mFileName);
+                }
                 status = BluetoothShare.STATUS_SUCCESS;
             } else {
-                if (D) Log.d(TAG, "Reading file failed at " + position + " of " + fileInfo.mLength);
+                if (D) {
+                    Log.d(TAG, "Reading file failed at " + position + " of " + fileInfo.mLength);
+                }
                 if (status == -1) {
                     status = BluetoothShare.STATUS_UNKNOWN_ERROR;
                 }
@@ -518,13 +543,16 @@
                 Log.e(TAG, "Error when closing stream after send");
             }
         }
+        BluetoothOppUtility.cancelNotification(mContext);
         return status;
     }
 
     private BluetoothOppReceiveFileInfo processShareInfo() {
-        if (D) Log.d(TAG, "processShareInfo() " + mInfo.mId);
-        BluetoothOppReceiveFileInfo fileInfo = BluetoothOppReceiveFileInfo.generateFileInfo(
-                mContext, mInfo.mId);
+        if (D) {
+            Log.d(TAG, "processShareInfo() " + mInfo.mId);
+        }
+        BluetoothOppReceiveFileInfo fileInfo =
+                BluetoothOppReceiveFileInfo.generateFileInfo(mContext, mInfo.mId);
         if (V) {
             Log.v(TAG, "Generate BluetoothOppReceiveFileInfo:");
             Log.v(TAG, "filename  :" + fileInfo.mFileName);
@@ -537,14 +565,20 @@
     @Override
     public int onConnect(HeaderSet request, HeaderSet reply) {
 
-        if (D) Log.d(TAG, "onConnect");
-        if (V) Constants.logHeader(request);
+        if (D) {
+            Log.d(TAG, "onConnect");
+        }
+        if (V) {
+            Constants.logHeader(request);
+        }
         Long objectCount = null;
         try {
-            byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
-            if (V) Log.v(TAG, "onConnect(): uuid =" + Arrays.toString(uuid));
-            if(uuid != null) {
-                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+            byte[] uuid = (byte[]) request.getHeader(HeaderSet.TARGET);
+            if (V) {
+                Log.v(TAG, "onConnect(): uuid =" + Arrays.toString(uuid));
+            }
+            if (uuid != null) {
+                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
             }
 
             objectCount = (Long) request.getHeader(HeaderSet.COUNT);
@@ -554,12 +588,11 @@
         }
         String destination;
         if (mTransport instanceof BluetoothObexTransport) {
-            destination = ((BluetoothObexTransport)mTransport).getRemoteAddress();
+            destination = ((BluetoothObexTransport) mTransport).getRemoteAddress();
         } else {
             destination = "FF:FF:FF:00:00:00";
         }
-        boolean isHandover = BluetoothOppManager.getInstance(mContext).
-                isWhitelisted(destination);
+        boolean isHandover = BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
         if (isHandover) {
             // Notify the handover requester file transfer has started
             Intent intent = new Intent(Constants.ACTION_HANDOVER_STARTED);
@@ -573,12 +606,19 @@
             mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION);
         }
         mTimestamp = System.currentTimeMillis();
+        mNumFilesAttemptedToReceive = 0;
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
     @Override
     public void onDisconnect(HeaderSet req, HeaderSet resp) {
-        if (D) Log.d(TAG, "onDisconnect");
+        if (D) {
+            Log.d(TAG, "onDisconnect");
+        }
+        if (mNumFilesAttemptedToReceive > 0) {
+            // Log incoming OPP transfer if more than one file is accepted by user
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.OPP);
+        }
         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
     }
 
@@ -590,18 +630,12 @@
 
     @Override
     public void onClose() {
-        if (D) Log.d(TAG, "onClose");
-        releaseWakeLocks();
-
-        if (mServerSocket != null) {
-            if (D) Log.d(TAG, "prepareForNewConnect");
-            mServerSocket.prepareForNewConnect();
+        if (D) {
+            Log.d(TAG, "onClose");
         }
-
-        NotificationManager nm =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        nm.cancel(BluetoothOppNotification.NOTIFICATION_ID_PROGRESS);
-
+        releaseWakeLocks();
+        mBluetoothOppService.acceptNewConnections();
+        BluetoothOppUtility.cancelNotification(mContext);
         /* onClose could happen even before start() where mCallback is set */
         if (mCallback != null) {
             Message msg = Message.obtain(mCallback);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppPreference.java b/src/com/android/bluetooth/opp/BluetoothOppPreference.java
index 11a3184..47053e8 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppPreference.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppPreference.java
@@ -32,14 +32,14 @@
 
 package com.android.bluetooth.opp;
 
-import java.util.HashMap;
-
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 import android.util.Log;
 
+import java.util.HashMap;
+
 /**
  * This class cache Bluetooth device name and channel locally. Its a temp
  * solution which should be replaced by bluetooth_devices in SettingsProvider
@@ -48,10 +48,10 @@
     private static final String TAG = "BluetoothOppPreference";
     private static final boolean V = Constants.VERBOSE;
 
-    private static BluetoothOppPreference INSTANCE;
+    private static BluetoothOppPreference sInstance;
 
     /* Used when obtaining a reference to the singleton instance. */
-    private static Object INSTANCE_LOCK = new Object();
+    private static final Object INSTANCE_LOCK = new Object();
 
     private boolean mInitialized;
 
@@ -67,27 +67,29 @@
 
     public static BluetoothOppPreference getInstance(Context context) {
         synchronized (INSTANCE_LOCK) {
-            if (INSTANCE == null) {
-                INSTANCE = new BluetoothOppPreference();
+            if (sInstance == null) {
+                sInstance = new BluetoothOppPreference();
             }
-            if (!INSTANCE.init(context)) {
+            if (!sInstance.init(context)) {
                 return null;
             }
-            return INSTANCE;
+            return sInstance;
         }
     }
 
     private boolean init(Context context) {
-        if (mInitialized)
+        if (mInitialized) {
             return true;
+        }
         mInitialized = true;
 
         mContext = context;
 
         mNamePreference = mContext.getSharedPreferences(Constants.BLUETOOTHOPP_NAME_PREFERENCE,
                 Context.MODE_PRIVATE);
-        mChannelPreference = mContext.getSharedPreferences(
-                Constants.BLUETOOTHOPP_CHANNEL_PREFERENCE, Context.MODE_PRIVATE);
+        mChannelPreference =
+                mContext.getSharedPreferences(Constants.BLUETOOTHOPP_CHANNEL_PREFERENCE,
+                        Context.MODE_PRIVATE);
 
         mNames = (HashMap<String, String>) mNamePreference.getAll();
         mChannels = (HashMap<String, Integer>) mChannelPreference.getAll();
@@ -114,18 +116,25 @@
 
     public int getChannel(BluetoothDevice remoteDevice, int uuid) {
         String key = getChannelKey(remoteDevice, uuid);
-        if (V) Log.v(TAG, "getChannel " + key);
+        if (V) {
+            Log.v(TAG, "getChannel " + key);
+        }
         Integer channel = null;
         if (mChannels != null) {
             channel = mChannels.get(key);
-            if (V) Log.v(TAG, "getChannel for " + remoteDevice + "_" + Integer.toHexString(uuid) +
-                        " as " + channel);
+            if (V) {
+                Log.v(TAG,
+                        "getChannel for " + remoteDevice + "_" + Integer.toHexString(uuid) + " as "
+                                + channel);
+            }
         }
         return (channel != null) ? channel : -1;
     }
 
     public void setName(BluetoothDevice remoteDevice, String name) {
-        if (V) Log.v(TAG, "Setname for " + remoteDevice + " to " + name);
+        if (V) {
+            Log.v(TAG, "Setname for " + remoteDevice + " to " + name);
+        }
         if (name != null && !name.equals(getName(remoteDevice))) {
             Editor ed = mNamePreference.edit();
             ed.putString(remoteDevice.getAddress(), name);
@@ -135,8 +144,10 @@
     }
 
     public void setChannel(BluetoothDevice remoteDevice, int uuid, int channel) {
-        if (V) Log.v(TAG, "Setchannel for " + remoteDevice + "_" + Integer.toHexString(uuid) + " to "
+        if (V) {
+            Log.v(TAG, "Setchannel for " + remoteDevice + "_" + Integer.toHexString(uuid) + " to "
                     + channel);
+        }
         if (channel != getChannel(remoteDevice, uuid)) {
             String key = getChannelKey(remoteDevice, uuid);
             Editor ed = mChannelPreference.edit();
diff --git a/src/com/android/bluetooth/opp/BluetoothOppProvider.java b/src/com/android/bluetooth/opp/BluetoothOppProvider.java
index c3ea704..4079a87 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppProvider.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppProvider.java
@@ -35,10 +35,9 @@
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.Intent;
+import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.SQLException;
-import android.content.UriMatcher;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
@@ -101,7 +100,7 @@
      */
     private final class DatabaseHelper extends SQLiteOpenHelper {
 
-        public DatabaseHelper(final Context context) {
+        DatabaseHelper(final Context context) {
             super(context, DB_NAME, null, DB_VERSION);
         }
 
@@ -110,42 +109,28 @@
          */
         @Override
         public void onCreate(final SQLiteDatabase db) {
-            if (V) Log.v(TAG, "populating new database");
+            if (V) {
+                Log.v(TAG, "populating new database");
+            }
             createTable(db);
         }
 
-        //TODO: use this function to check garbage transfer left in db, for example,
-        // a crash incoming file
-        /*
-         * (not a javadoc comment) Checks data integrity when opening the
-         * database.
-         */
-        /*
-         * @Override public void onOpen(final SQLiteDatabase db) {
-         * super.onOpen(db); }
-         */
-
         /**
          * Updates the database format when a content provider is used with a
          * database that was created with a different format.
          */
-        // Note: technically, this could also be a downgrade, so if we want
-        // to gracefully handle upgrades we should be careful about
-        // what to do on downgrades.
         @Override
         public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) {
             if (oldV == DB_VERSION_NOP_UPGRADE_FROM) {
-                if (newV == DB_VERSION_NOP_UPGRADE_TO) { // that's a no-op
-                    // upgrade.
+                if (newV == DB_VERSION_NOP_UPGRADE_TO) {
                     return;
                 }
-                // NOP_FROM and NOP_TO are identical, just in different
-                // codelines. Upgrading
-                // from NOP_FROM is the same as upgrading from NOP_TO.
+                // NOP_FROM and NOP_TO are identical, just in different code lines.
+                // Upgrading from NOP_FROM is the same as upgrading from NOP_TO.
                 oldV = DB_VERSION_NOP_UPGRADE_TO;
             }
-            Log.i(TAG, "Upgrading downloads database from version " + oldV + " to "
-                    + newV + ", which will destroy all old data");
+            Log.i(TAG, "Upgrading downloads database from version " + oldV + " to " + newV
+                    + ", which will destroy all old data");
             dropTable(db);
             createTable(db);
         }
@@ -165,7 +150,7 @@
                     + BluetoothShare.TIMESTAMP + " INTEGER," + Constants.MEDIA_SCANNED
                     + " INTEGER); ");
         } catch (SQLException ex) {
-            Log.e(TAG, "couldn't create table in downloads database");
+            Log.e(TAG, "createTable: Failed.");
             throw ex;
         }
     }
@@ -174,7 +159,7 @@
         try {
             db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
         } catch (SQLException ex) {
-            Log.e(TAG, "couldn't drop table in downloads database");
+            Log.e(TAG, "dropTable: Failed.");
             throw ex;
         }
     }
@@ -183,34 +168,30 @@
     public String getType(Uri uri) {
         int match = sURIMatcher.match(uri);
         switch (match) {
-            case SHARES: {
+            case SHARES:
                 return SHARE_LIST_TYPE;
-            }
-            case SHARES_ID: {
+            case SHARES_ID:
                 return SHARE_TYPE;
-            }
-            default: {
-                if (D) Log.d(TAG, "calling getType on an unknown URI: " + uri);
-                throw new IllegalArgumentException("Unknown URI: " + uri);
-            }
+            default:
+                throw new IllegalArgumentException("Unknown URI in getType(): " + uri);
         }
     }
 
-    private static final void copyString(String key, ContentValues from, ContentValues to) {
+    private static void copyString(String key, ContentValues from, ContentValues to) {
         String s = from.getAsString(key);
         if (s != null) {
             to.put(key, s);
         }
     }
 
-    private static final void copyInteger(String key, ContentValues from, ContentValues to) {
+    private static void copyInteger(String key, ContentValues from, ContentValues to) {
         Integer i = from.getAsInteger(key);
         if (i != null) {
             to.put(key, i);
         }
     }
 
-    private static final void copyLong(String key, ContentValues from, ContentValues to) {
+    private static void copyLong(String key, ContentValues from, ContentValues to) {
         Long i = from.getAsLong(key);
         if (i != null) {
             to.put(key, i);
@@ -222,8 +203,7 @@
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 
         if (sURIMatcher.match(uri) != SHARES) {
-            if (D) Log.d(TAG, "calling insert on an unknown/invalid URI: " + uri);
-            throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
+            throw new IllegalArgumentException("insert: Unknown/Invalid URI " + uri);
         }
 
         ContentValues filteredValues = new ContentValues();
@@ -241,7 +221,7 @@
         Integer dir = values.getAsInteger(BluetoothShare.DIRECTION);
         Integer con = values.getAsInteger(BluetoothShare.USER_CONFIRMATION);
 
-        if (values.getAsInteger(BluetoothShare.DIRECTION) == null) {
+        if (dir == null) {
             dir = BluetoothShare.DIRECTION_OUTBOUND;
         }
         if (dir == BluetoothShare.DIRECTION_OUTBOUND && con == null) {
@@ -263,21 +243,17 @@
         filteredValues.put(BluetoothShare.TIMESTAMP, ts);
 
         Context context = getContext();
-        context.startService(new Intent(context, BluetoothOppService.class));
 
         long rowID = db.insert(DB_TABLE, null, filteredValues);
 
-        Uri ret = null;
+        if (rowID == -1) {
+            Log.w(TAG, "couldn't insert " + uri + "into btopp database");
+            return null;
+        }
 
-        if (rowID != -1) {
-            context.startService(new Intent(context, BluetoothOppService.class));
-            ret = Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID);
-            context.getContentResolver().notifyChange(uri, null);
-        } else {
-            if (D) Log.d(TAG, "couldn't insert into btopp database");
-            }
+        context.getContentResolver().notifyChange(uri, null);
 
-        return ret;
+        return Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID);
     }
 
     @Override
@@ -296,20 +272,16 @@
 
         int match = sURIMatcher.match(uri);
         switch (match) {
-            case SHARES: {
+            case SHARES:
                 qb.setTables(DB_TABLE);
                 break;
-            }
-            case SHARES_ID: {
+            case SHARES_ID:
                 qb.setTables(DB_TABLE);
                 qb.appendWhere(BluetoothShare._ID + "=");
                 qb.appendWhere(uri.getPathSegments().get(1));
                 break;
-            }
-            default: {
-                if (D) Log.d(TAG, "querying unknown URI: " + uri);
+            default:
                 throw new IllegalArgumentException("Unknown URI: " + uri);
-            }
         }
 
         if (V) {
@@ -356,13 +328,12 @@
 
         Cursor ret = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
 
-        if (ret != null) {
-            ret.setNotificationUri(getContext().getContentResolver(), uri);
-            if (V) Log.v(TAG, "created cursor " + ret + " on behalf of ");// +
-        } else {
-            if (D) Log.d(TAG, "query failed in downloads database");
-            }
+        if (ret == null) {
+            Log.w(TAG, "query failed in downloads database");
+            return null;
+        }
 
+        ret.setNotificationUri(getContext().getContentResolver(), uri);
         return ret;
     }
 
@@ -370,8 +341,8 @@
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 
-        int count;
-        long rowId = 0;
+        int count = 0;
+        long rowId;
 
         int match = sURIMatcher.match(uri);
         switch (match) {
@@ -395,15 +366,11 @@
 
                 if (values.size() > 0) {
                     count = db.update(DB_TABLE, values, myWhere, selectionArgs);
-                } else {
-                    count = 0;
                 }
                 break;
             }
-            default: {
-                if (D) Log.d(TAG, "updating unknown/invalid URI: " + uri);
-                throw new UnsupportedOperationException("Cannot update URI: " + uri);
-            }
+            default:
+                throw new UnsupportedOperationException("Cannot update unknown URI: " + uri);
         }
         getContext().getContentResolver().notifyChange(uri, null);
 
@@ -437,10 +404,8 @@
                 count = db.delete(DB_TABLE, myWhere, selectionArgs);
                 break;
             }
-            default: {
-                if (D) Log.d(TAG, "deleting unknown/invalid URI: " + uri);
-                throw new UnsupportedOperationException("Cannot delete URI: " + uri);
-            }
+            default:
+                throw new UnsupportedOperationException("Cannot delete unknown URI: " + uri);
         }
         getContext().getContentResolver().notifyChange(uri, null);
         return count;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppReceiveFileInfo.java b/src/com/android/bluetooth/opp/BluetoothOppReceiveFileInfo.java
index 02e01f5..a3a660d 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppReceiveFileInfo.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppReceiveFileInfo.java
@@ -32,13 +32,6 @@
 
 package com.android.bluetooth.opp;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Random;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -49,6 +42,12 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Random;
+
 /**
  * This class stores information about a single receiving file. It will only be
  * used for inbounds share, e.g. receive a file to determine a correct save file
@@ -99,7 +98,7 @@
         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);
         String filename = null, hint = null, mimeType = null;
         long length = 0;
-        Cursor metadataCursor = contentResolver.query(contentUri, new String[] {
+        Cursor metadataCursor = contentResolver.query(contentUri, new String[]{
                 BluetoothShare.FILENAME_HINT, BluetoothShare.TOTAL_BYTES, BluetoothShare.MIMETYPE
         }, null, null, null);
         if (metadataCursor != null) {
@@ -121,13 +120,17 @@
             String root = Environment.getExternalStorageDirectory().getPath();
             base = new File(root + Constants.DEFAULT_STORE_SUBDIR);
             if (!base.isDirectory() && !base.mkdir()) {
-                if (D) Log.d(Constants.TAG, "Receive File aborted - can't create base directory "
-                            + base.getPath());
+                if (D) {
+                    Log.d(Constants.TAG,
+                            "Receive File aborted - can't create base directory " + base.getPath());
+                }
                 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
             }
             stat = new StatFs(base.getPath());
         } else {
-            if (D) Log.d(Constants.TAG, "Receive File aborted - no external storage");
+            if (D) {
+                Log.d(Constants.TAG, "Receive File aborted - no external storage");
+            }
             return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_NO_SDCARD);
         }
 
@@ -137,7 +140,9 @@
          * system by a few blocks).
          */
         if (stat.getBlockSizeLong() * (stat.getAvailableBlocksLong() - 4) < length) {
-            if (D) Log.d(Constants.TAG, "Receive File aborted - not enough free space");
+            if (D) {
+                Log.d(Constants.TAG, "Receive File aborted - not enough free space");
+            }
             return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_SDCARD_FULL);
         }
 
@@ -159,7 +164,9 @@
             extension = filename.substring(dotIndex);
             filename = filename.substring(0, dotIndex);
         }
-        if (D) Log.d(Constants.TAG, " File Name " + filename);
+        if (D) {
+            Log.d(Constants.TAG, " File Name " + filename);
+        }
 
         if (filename.getBytes().length > OPP_LENGTH_OF_FILE_NAME) {
           /* Including extn of the file, Linux supports 255 character as a maximum length of the
@@ -169,18 +176,20 @@
            * more than 255 characters, But the server rejects the card just because the length of
            * vcf file name received exceeds 255 Characters.
            */
-              Log.i(Constants.TAG, " File Name Length :" + filename.length());
-              Log.i(Constants.TAG, " File Name Length in Bytes:" + filename.getBytes().length);
+            Log.i(Constants.TAG, " File Name Length :" + filename.length());
+            Log.i(Constants.TAG, " File Name Length in Bytes:" + filename.getBytes().length);
 
-          try {
-              byte[] oldfilename = filename.getBytes("UTF-8");
-              byte[] newfilename = new byte[OPP_LENGTH_OF_FILE_NAME];
-              System.arraycopy(oldfilename, 0, newfilename, 0, OPP_LENGTH_OF_FILE_NAME);
-              filename = new String(newfilename, "UTF-8");
-          } catch (UnsupportedEncodingException e) {
-              Log.e(Constants.TAG, "Exception: " + e);
-          }
-          if (D) Log.d(Constants.TAG, "File name is too long. Name is truncated as: " + filename);
+            try {
+                byte[] oldfilename = filename.getBytes("UTF-8");
+                byte[] newfilename = new byte[OPP_LENGTH_OF_FILE_NAME];
+                System.arraycopy(oldfilename, 0, newfilename, 0, OPP_LENGTH_OF_FILE_NAME);
+                filename = new String(newfilename, "UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                Log.e(Constants.TAG, "Exception: " + e);
+            }
+            if (D) {
+                Log.d(Constants.TAG, "File name is too long. Name is truncated as: " + filename);
+            }
         }
 
         filename = base.getPath() + File.separator + filename;
@@ -191,7 +200,9 @@
             // If this second check fails, then we better reject the transfer
             return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
         }
-        if (V) Log.v(Constants.TAG, "Generated received filename " + fullfilename);
+        if (V) {
+            Log.v(Constants.TAG, "Generated received filename " + fullfilename);
+        }
 
         if (fullfilename != null) {
             try {
@@ -200,16 +211,20 @@
                 // update display name
                 if (index > 0) {
                     String displayName = fullfilename.substring(index);
-                    if (V) Log.v(Constants.TAG, "New display name " + displayName);
+                    if (V) {
+                        Log.v(Constants.TAG, "New display name " + displayName);
+                    }
                     ContentValues updateValues = new ContentValues();
                     updateValues.put(BluetoothShare.FILENAME_HINT, displayName);
                     context.getContentResolver().update(contentUri, updateValues, null, null);
 
                 }
-                return new BluetoothOppReceiveFileInfo(fullfilename, length, new FileOutputStream(
-                        fullfilename), 0);
+                return new BluetoothOppReceiveFileInfo(fullfilename, length,
+                        new FileOutputStream(fullfilename), 0);
             } catch (IOException e) {
-                if (D) Log.e(Constants.TAG, "Error when creating file " + fullfilename);
+                if (D) {
+                    Log.e(Constants.TAG, "Error when creating file " + fullfilename);
+                }
                 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
             }
         } else {
@@ -222,8 +237,8 @@
         try {
             File receiveFile = new File(uniqueFileName);
             if (sDesiredStoragePath == null) {
-                sDesiredStoragePath = Environment.getExternalStorageDirectory().getPath() +
-                    Constants.DEFAULT_STORE_SUBDIR;
+                sDesiredStoragePath = Environment.getExternalStorageDirectory().getPath()
+                        + Constants.DEFAULT_STORE_SUBDIR;
             }
             String canonicalPath = receiveFile.getCanonicalPath();
 
@@ -244,7 +259,7 @@
         if (!new File(fullfilename).exists()) {
             return fullfilename;
         }
-        filename = filename + Constants.filename_SEQUENCE_SEPARATOR;
+        filename = filename + Constants.FILENAME_SEQUENCE_SEPARATOR;
         /*
          * This number is used to generate partially randomized filenames to
          * avoid collisions. It starts at 1. The next 9 iterations increment it
@@ -264,7 +279,9 @@
                 if (!new File(fullfilename).exists()) {
                     return fullfilename;
                 }
-                if (V) Log.v(Constants.TAG, "file with sequence number " + sequence + " exists");
+                if (V) {
+                    Log.v(Constants.TAG, "file with sequence number " + sequence + " exists");
+                }
                 sequence += rnd.nextInt(magnitude) + 1;
             }
         }
@@ -284,7 +301,9 @@
             // Replace illegal fat filesystem characters from the
             // filename hint i.e. :"<>*?| with something safe.
             hint = hint.replaceAll("[:\"<>*?|]", "_");
-            if (V) Log.v(Constants.TAG, "getting filename from hint");
+            if (V) {
+                Log.v(Constants.TAG, "getting filename from hint");
+            }
             int index = hint.lastIndexOf('/') + 1;
             if (index > 0) {
                 filename = hint.substring(index);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
index ebfd6c2..73d0194 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
@@ -32,14 +32,10 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
 import android.app.NotificationManager;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothDevicePicker;
 import android.content.BroadcastReceiver;
-import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -48,6 +44,8 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import com.android.bluetooth.R;
+
 /**
  * Receives and handles: system broadcasts; Intents from other applications;
  * Intents from OppService; Intents from modules in Opp application layer.
@@ -66,7 +64,9 @@
 
             BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
 
-            if (D) Log.d(TAG, "Received BT device selected intent, bt device: " + remoteDevice);
+            if (D) {
+                Log.d(TAG, "Received BT device selected intent, bt device: " + remoteDevice);
+            }
 
             if (remoteDevice == null) {
                 mOppManager.cleanUpSendingFileInfo();
@@ -87,7 +87,9 @@
             }
             Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
         } else if (action.equals(Constants.ACTION_INCOMING_FILE_CONFIRM)) {
-            if (V) Log.v(TAG, "Receiver ACTION_INCOMING_FILE_CONFIRM");
+            if (V) {
+                Log.v(TAG, "Receiver ACTION_INCOMING_FILE_CONFIRM");
+            }
 
             Uri uri = intent.getData();
             Intent in = new Intent(context, BluetoothOppIncomingFileConfirmActivity.class);
@@ -96,7 +98,9 @@
             context.startActivity(in);
 
         } else if (action.equals(Constants.ACTION_DECLINE)) {
-            if (V) Log.v(TAG, "Receiver ACTION_DECLINE");
+            if (V) {
+                Log.v(TAG, "Receiver ACTION_DECLINE");
+            }
 
             Uri uri = intent.getData();
             ContentValues values = new ContentValues();
@@ -105,11 +109,14 @@
             cancelNotification(context, BluetoothOppNotification.NOTIFICATION_ID_PROGRESS);
 
         } else if (action.equals(Constants.ACTION_ACCEPT)) {
-            if (V) Log.v(TAG, "Receiver ACTION_ACCEPT");
+            if (V) {
+                Log.v(TAG, "Receiver ACTION_ACCEPT");
+            }
 
             Uri uri = intent.getData();
             ContentValues values = new ContentValues();
-            values.put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_CONFIRMED);
+            values.put(BluetoothShare.USER_CONFIRMATION,
+                    BluetoothShare.USER_CONFIRMATION_CONFIRMED);
             context.getContentResolver().update(uri, values, null, null);
         } else if (action.equals(Constants.ACTION_OPEN) || action.equals(Constants.ACTION_LIST)) {
             if (V) {
@@ -136,27 +143,33 @@
                 BluetoothOppUtility.updateVisibilityToHidden(context, uri);
             } else {
                 Intent in = new Intent(context, BluetoothOppTransferActivity.class);
-                in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                 in.setDataAndNormalize(uri);
                 context.startActivity(in);
             }
 
         } else if (action.equals(Constants.ACTION_OPEN_OUTBOUND_TRANSFER)) {
-            if (V) Log.v(TAG, "Received ACTION_OPEN_OUTBOUND_TRANSFER.");
+            if (V) {
+                Log.v(TAG, "Received ACTION_OPEN_OUTBOUND_TRANSFER.");
+            }
 
             Intent in = new Intent(context, BluetoothOppTransferHistory.class);
             in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
             in.putExtra("direction", BluetoothShare.DIRECTION_OUTBOUND);
             context.startActivity(in);
         } else if (action.equals(Constants.ACTION_OPEN_INBOUND_TRANSFER)) {
-            if (V) Log.v(TAG, "Received ACTION_OPEN_INBOUND_TRANSFER.");
+            if (V) {
+                Log.v(TAG, "Received ACTION_OPEN_INBOUND_TRANSFER.");
+            }
 
             Intent in = new Intent(context, BluetoothOppTransferHistory.class);
             in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
             in.putExtra("direction", BluetoothShare.DIRECTION_INBOUND);
             context.startActivity(in);
         } else if (action.equals(Constants.ACTION_OPEN_RECEIVED_FILES)) {
-            if (V) Log.v(TAG, "Received ACTION_OPEN_RECEIVED_FILES.");
+            if (V) {
+                Log.v(TAG, "Received ACTION_OPEN_RECEIVED_FILES.");
+            }
 
             Intent in = new Intent(context, BluetoothOppTransferHistory.class);
             in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -164,34 +177,43 @@
             in.putExtra(Constants.EXTRA_SHOW_ALL_FILES, true);
             context.startActivity(in);
         } else if (action.equals(Constants.ACTION_HIDE)) {
-            if (V) Log.v(TAG, "Receiver hide for " + intent.getData());
-            Cursor cursor = context.getContentResolver().query(intent.getData(), null, null, null,
-                    null);
+            if (V) {
+                Log.v(TAG, "Receiver hide for " + intent.getData());
+            }
+            Cursor cursor =
+                    context.getContentResolver().query(intent.getData(), null, null, null, null);
             if (cursor != null) {
                 if (cursor.moveToFirst()) {
                     int visibilityColumn = cursor.getColumnIndexOrThrow(BluetoothShare.VISIBILITY);
                     int visibility = cursor.getInt(visibilityColumn);
-                    int userConfirmationColumn = cursor
-                            .getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION);
+                    int userConfirmationColumn =
+                            cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION);
                     int userConfirmation = cursor.getInt(userConfirmationColumn);
                     if (((userConfirmation == BluetoothShare.USER_CONFIRMATION_PENDING))
                             && visibility == BluetoothShare.VISIBILITY_VISIBLE) {
                         ContentValues values = new ContentValues();
                         values.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_HIDDEN);
                         context.getContentResolver().update(intent.getData(), values, null, null);
-                        if (V) Log.v(TAG, "Action_hide received and db updated");
+                        if (V) {
+                            Log.v(TAG, "Action_hide received and db updated");
                         }
+                    }
                 }
                 cursor.close();
             }
         } else if (action.equals(Constants.ACTION_COMPLETE_HIDE)) {
-            if (V) Log.v(TAG, "Receiver ACTION_COMPLETE_HIDE");
+            if (V) {
+                Log.v(TAG, "Receiver ACTION_COMPLETE_HIDE");
+            }
             ContentValues updateValues = new ContentValues();
             updateValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_HIDDEN);
-            context.getContentResolver().update(BluetoothShare.CONTENT_URI, updateValues,
-                    BluetoothOppNotification.WHERE_COMPLETED, null);
+            context.getContentResolver()
+                    .update(BluetoothShare.CONTENT_URI, updateValues,
+                            BluetoothOppNotification.WHERE_COMPLETED, null);
         } else if (action.equals(BluetoothShare.TRANSFER_COMPLETED_ACTION)) {
-            if (V) Log.v(TAG, "Receiver Transfer Complete Intent for " + intent.getData());
+            if (V) {
+                Log.v(TAG, "Receiver Transfer Complete Intent for " + intent.getData());
+            }
 
             String toastMsg = null;
             BluetoothOppTransferInfo transInfo = new BluetoothOppTransferInfo();
@@ -233,19 +255,21 @@
                 if (transInfo.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                     toastMsg = context.getString(R.string.notification_sent, transInfo.mFileName);
                 } else if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND) {
-                    toastMsg = context.getString(R.string.notification_received,
-                            transInfo.mFileName);
+                    toastMsg =
+                            context.getString(R.string.notification_received, transInfo.mFileName);
                 }
 
             } else if (BluetoothShare.isStatusError(transInfo.mStatus)) {
                 if (transInfo.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
-                    toastMsg = context.getString(R.string.notification_sent_fail,
-                            transInfo.mFileName);
+                    toastMsg =
+                            context.getString(R.string.notification_sent_fail, transInfo.mFileName);
                 } else if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND) {
                     toastMsg = context.getString(R.string.download_fail_line1);
                 }
             }
-            if (V) Log.v(TAG, "Toast msg == " + toastMsg);
+            if (V) {
+                Log.v(TAG, "Toast msg == " + toastMsg);
+            }
             if (toastMsg != null) {
                 Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
             }
@@ -253,10 +277,14 @@
     }
 
     private void cancelNotification(Context context, int id) {
-        NotificationManager notMgr = (NotificationManager)context
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-        if (notMgr == null) return;
+        NotificationManager notMgr =
+                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        if (notMgr == null) {
+            return;
+        }
         notMgr.cancel(id);
-        if (V) Log.v(TAG, "notMgr.cancel called");
+        if (V) {
+            Log.v(TAG, "notMgr.cancel called");
+        }
     }
 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
index 39a5909..6d7fe95 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
@@ -42,6 +42,8 @@
 import android.util.EventLog;
 import android.util.Log;
 
+import com.android.bluetooth.R;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -58,8 +60,8 @@
 
 
     /** Reusable SendFileInfo for error status. */
-    static final BluetoothOppSendFileInfo SEND_FILE_INFO_ERROR = new BluetoothOppSendFileInfo(
-            null, null, 0, null, BluetoothShare.STATUS_FILE_ERROR);
+    static final BluetoothOppSendFileInfo SEND_FILE_INFO_ERROR =
+            new BluetoothOppSendFileInfo(null, null, 0, null, BluetoothShare.STATUS_FILE_ERROR);
 
     /** readable media file name */
     public final String mFileName;
@@ -97,8 +99,8 @@
         mStatus = status;
     }
 
-    public static BluetoothOppSendFileInfo generateFileInfo(
-            Context context, Uri uri, String type, boolean fromExternal) {
+    public static BluetoothOppSendFileInfo generateFileInfo(Context context, Uri uri, String type,
+            boolean fromExternal) {
         ContentResolver contentResolver = context.getContentResolver();
         String scheme = uri.getScheme();
         String fileName = null;
@@ -111,7 +113,7 @@
             contentType = contentResolver.getType(uri);
             Cursor metadataCursor;
             try {
-                metadataCursor = contentResolver.query(uri, new String[] {
+                metadataCursor = contentResolver.query(uri, new String[]{
                         OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
                 }, null, null, null);
             } catch (SQLiteException e) {
@@ -125,11 +127,17 @@
             if (metadataCursor != null) {
                 try {
                     if (metadataCursor.moveToFirst()) {
-                        fileName = metadataCursor.getString(
-                                metadataCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
-                        length = metadataCursor.getLong(
-                                metadataCursor.getColumnIndex(OpenableColumns.SIZE));
-                        if (D) Log.d(TAG, "fileName = " + fileName + " length = " + length);
+                        int indexName = metadataCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+                        int indexSize = metadataCursor.getColumnIndex(OpenableColumns.SIZE);
+                        if (indexName != -1) {
+                            fileName = metadataCursor.getString(indexName);
+                        }
+                        if (indexSize != -1) {
+                            length = metadataCursor.getLong(indexSize);
+                        }
+                        if (D) {
+                            Log.d(TAG, "fileName = " + fileName + " length = " + length);
+                        }
                     }
                 } finally {
                     metadataCursor.close();
@@ -138,6 +146,7 @@
             if (fileName == null) {
                 // use last segment of URI if DISPLAY_NAME query fails
                 fileName = uri.getLastPathSegment();
+                if (D) Log.d(TAG, "fileName from URI :" + fileName);
             }
         } else if ("file".equals(scheme)) {
             if (uri.getPath() == null) {
@@ -146,8 +155,8 @@
             }
             if (fromExternal && !BluetoothOppUtility.isInExternalStorageDir(uri)) {
                 EventLog.writeEvent(0x534e4554, "35310991", -1, uri.getPath());
-                Log.e(TAG,
-                        "File based URI not in Environment.getExternalStorageDirectory() is not allowed.");
+                Log.e(TAG, "File based URI not in Environment.getExternalStorageDirectory() is not "
+                        + "allowed.");
                 return SEND_FILE_INFO_ERROR;
             }
             fileName = uri.getLastPathSegment();
@@ -168,8 +177,8 @@
                 AssetFileDescriptor fd = contentResolver.openAssetFileDescriptor(uri, "r");
                 long statLength = fd.getLength();
                 if (length != statLength && statLength > 0) {
-                    Log.e(TAG, "Content provider length is wrong (" + Long.toString(length) +
-                            "), using stat length (" + Long.toString(statLength) + ")");
+                    Log.e(TAG, "Content provider length is wrong (" + Long.toString(length)
+                            + "), using stat length (" + Long.toString(statLength) + ")");
                     length = statLength;
                 }
 
@@ -183,8 +192,7 @@
                     // by reading through the entire stream
                     if (length == 0) {
                         length = getStreamSize(is);
-                        Log.w(TAG, "File length not provided. Length from stream = "
-                                   + length);
+                        Log.w(TAG, "File length not provided. Length from stream = " + length);
                         // Reset the stream
                         fd = contentResolver.openAssetFileDescriptor(uri, "r");
                         is = fd.createInputStream();
@@ -223,9 +231,9 @@
             Log.e(TAG, "Could not determine size of file");
             return SEND_FILE_INFO_ERROR;
         } else if (length > 0xffffffffL) {
-            String msg = "Files bigger than 4GB can't be transferred";
-            Log.e(TAG, msg);
-            throw new IllegalArgumentException(msg);
+            Log.e(TAG, "File of size: " + length + " bytes can't be transferred");
+            throw new IllegalArgumentException(context
+                .getString(R.string.bluetooth_opp_file_limit_exceeded));
         }
 
         return new BluetoothOppSendFileInfo(fileName, contentType, length, is, 0);
@@ -233,7 +241,7 @@
 
     private static long getStreamSize(FileInputStream is) throws IOException {
         long length = 0;
-        byte unused[] = new byte[4096];
+        byte[] unused = new byte[4096];
         int bytesRead = is.read(unused, 0, 4096);
         while (bytesRead != -1) {
             length += bytesRead;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index 7f94820..383a497 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -32,14 +32,9 @@
 
 package com.android.bluetooth.opp;
 
-import com.google.android.collect.Lists;
-import javax.obex.ObexTransport;
-
-import android.app.Service;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothDevicePicker;
-import android.bluetooth.BluetoothServerSocket;
 import android.bluetooth.BluetoothSocket;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -53,23 +48,28 @@
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
-import android.os.PowerManager;
-import android.util.Log;
-import android.widget.Toast;
 import android.os.Process;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
 
 import com.android.bluetooth.BluetoothObexTransport;
 import com.android.bluetooth.IObexConnectionHandler;
 import com.android.bluetooth.ObexServerSockets;
 import com.android.bluetooth.btservice.ProfileService;
-import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
+import com.android.bluetooth.sdp.SdpManager;
+
+import com.google.android.collect.Lists;
 
 import java.io.IOException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import com.android.bluetooth.sdp.SdpManager;
+import java.util.Date;
+import java.util.Locale;
+
+import javax.obex.ObexTransport;
 
 /**
  * Performs the background Bluetooth OPP transfer. It also starts thread to
@@ -81,21 +81,24 @@
     private static final boolean V = Constants.VERBOSE;
 
     private static final byte[] SUPPORTED_OPP_FORMAT = {
-            0x01 /* vCard 2.1 */, 0x02 /* vCard 3.0 */, 0x03 /* vCal 1.0 */, 0x04 /* iCal 2.0 */,
+            0x01 /* vCard 2.1 */,
+            0x02 /* vCard 3.0 */,
+            0x03 /* vCal 1.0 */,
+            0x04 /* iCal 2.0 */,
             (byte) 0xFF /* Any type of object */
     };
 
-    private boolean userAccepted = false;
-
     private class BluetoothShareContentObserver extends ContentObserver {
 
-        public BluetoothShareContentObserver() {
+        BluetoothShareContentObserver() {
             super(new Handler());
         }
 
         @Override
         public void onChange(boolean selfChange) {
-            if (V) Log.v(TAG, "ContentObserver received notification");
+            if (V) {
+                Log.v(TAG, "ContentObserver received notification");
+            }
             updateFromProvider();
         }
     }
@@ -114,7 +117,7 @@
 
     private ArrayList<BluetoothOppShareInfo> mShares;
 
-    private ArrayList<BluetoothOppBatch> mBatchs;
+    private ArrayList<BluetoothOppBatch> mBatches;
 
     private BluetoothOppTransfer mTransfer;
 
@@ -131,18 +134,39 @@
      */
     private CharArrayBuffer mNewChars;
 
-    private PowerManager mPowerManager;
-
-    private boolean mListenStarted = false;
+    private boolean mListenStarted;
 
     private boolean mMediaScanInProgress;
 
-    private int mIncomingRetries = 0;
+    private int mIncomingRetries;
 
-    private ObexTransport mPendingConnection = null;
+    private ObexTransport mPendingConnection;
 
     private int mOppSdpHandle = -1;
 
+    boolean mAcceptNewConnections;
+
+    private BluetoothAdapter mAdapter;
+
+    private static final String INVISIBLE =
+            BluetoothShare.VISIBILITY + "=" + BluetoothShare.VISIBILITY_HIDDEN;
+
+    private static final String WHERE_INBOUND_SUCCESS =
+            BluetoothShare.DIRECTION + "=" + BluetoothShare.DIRECTION_INBOUND + " AND "
+                    + BluetoothShare.STATUS + "=" + BluetoothShare.STATUS_SUCCESS + " AND "
+                    + INVISIBLE;
+
+    private static final String WHERE_CONFIRM_PENDING_INBOUND =
+            BluetoothShare.DIRECTION + "=" + BluetoothShare.DIRECTION_INBOUND + " AND "
+                    + BluetoothShare.USER_CONFIRMATION + "="
+                    + BluetoothShare.USER_CONFIRMATION_PENDING;
+
+    private static final String WHERE_INVISIBLE_UNCONFIRMED =
+            "(" + BluetoothShare.STATUS + ">=" + BluetoothShare.STATUS_SUCCESS + " AND " + INVISIBLE
+                    + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")";
+
+    private static BluetoothOppService sBluetoothOppService;
+
     /*
      * TODO No support for queue incoming from multiple devices.
      * Make an array list of server session to support receiving queue from
@@ -152,23 +176,30 @@
 
     @Override
     protected IProfileServiceBinder initBinder() {
-        return null;
+        return new OppBinder(this);
+    }
+
+    private static class OppBinder extends Binder implements IProfileServiceBinder {
+
+        OppBinder(BluetoothOppService service) {
+        }
+
+        @Override
+        public void cleanup() {
+        }
     }
 
     @Override
     protected void create() {
-        if (V) Log.v(TAG, "onCreate");
+        if (V) {
+            Log.v(TAG, "onCreate");
+        }
         mShares = Lists.newArrayList();
-        mBatchs = Lists.newArrayList();
-        mObserver = new BluetoothShareContentObserver();
-        getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
+        mBatches = Lists.newArrayList();
         mBatchId = 1;
-        mNotifier = new BluetoothOppNotification(this);
-        mNotifier.mNotificationMgr.cancelAll();
-        mNotifier.updateNotification();
-
         final ContentResolver contentResolver = getContentResolver();
         new Thread("trimDatabase") {
+            @Override
             public void run() {
                 trimDatabase(contentResolver);
             }
@@ -177,24 +208,40 @@
         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
         registerReceiver(mBluetoothReceiver, filter);
 
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
         synchronized (BluetoothOppService.this) {
             if (mAdapter == null) {
                 Log.w(TAG, "Local BT device is not enabled");
             }
         }
-        if (V) BluetoothOppPreference.getInstance(this).dump();
-        updateFromProvider();
+        if (V) {
+            BluetoothOppPreference preference = BluetoothOppPreference.getInstance(this);
+            if (preference != null) {
+                preference.dump();
+            } else {
+                Log.w(TAG, "BluetoothOppPreference.getInstance returned null.");
+            }
+        }
     }
 
     @Override
     public boolean start() {
-        if (V) Log.v(TAG, "start()");
+        if (V) {
+            Log.v(TAG, "start()");
+        }
+        mObserver = new BluetoothShareContentObserver();
+        getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
+        mNotifier = new BluetoothOppNotification(this);
+        mNotifier.mNotificationMgr.cancelAll();
+        mNotifier.updateNotification();
         updateFromProvider();
+        setBluetoothOppService(this);
         return true;
     }
 
     @Override
     public boolean stop() {
+        setBluetoothOppService(null);
         mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
         return true;
     }
@@ -202,13 +249,55 @@
     private void startListener() {
         if (!mListenStarted) {
             if (mAdapter.isEnabled()) {
-                if (V) Log.v(TAG, "Starting RfcommListener");
+                if (V) {
+                    Log.v(TAG, "Starting RfcommListener");
+                }
                 mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
                 mListenStarted = true;
             }
         }
     }
 
+    @Override
+    public void dump(StringBuilder sb) {
+        super.dump(sb);
+        if (mShares.size() > 0) {
+            println(sb, "Shares:");
+            for (BluetoothOppShareInfo info : mShares) {
+                String dir = info.mDirection == BluetoothShare.DIRECTION_OUTBOUND ? " -> " : " <- ";
+                SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
+                Date date = new Date(info.mTimestamp);
+                println(sb, "  " + format.format(date) + dir + info.mCurrentBytes + "/"
+                        + info.mTotalBytes);
+            }
+        }
+    }
+
+    /**
+     * Get the current instance of {@link BluetoothOppService}
+     *
+     * @return current instance of {@link BluetoothOppService}
+     */
+    @VisibleForTesting
+    public static synchronized BluetoothOppService getBluetoothOppService() {
+        if (sBluetoothOppService == null) {
+            Log.w(TAG, "getBluetoothOppService(): service is null");
+            return null;
+        }
+        if (!sBluetoothOppService.isAvailable()) {
+            Log.w(TAG, "getBluetoothOppService(): service is not available");
+            return null;
+        }
+        return sBluetoothOppService;
+    }
+
+    private static synchronized void setBluetoothOppService(BluetoothOppService instance) {
+        if (D) {
+            Log.d(TAG, "setBluetoothOppService(): set to: " + instance);
+        }
+        sBluetoothOppService = instance;
+    }
+
     private static final int START_LISTENER = 1;
 
     private static final int MEDIA_SCANNED = 2;
@@ -229,15 +318,16 @@
                     stopListeners();
                     mListenStarted = false;
                     //Stop Active INBOUND Transfer
-                    if(mServerTransfer != null){
-                       mServerTransfer.onBatchCanceled();
-                       mServerTransfer =null;
+                    if (mServerTransfer != null) {
+                        mServerTransfer.onBatchCanceled();
+                        mServerTransfer = null;
                     }
                     //Stop Active OUTBOUND Transfer
-                    if(mTransfer != null){
-                       mTransfer.onBatchCanceled();
-                       mTransfer =null;
+                    if (mTransfer != null) {
+                        mTransfer.onBatchCanceled();
+                        mTransfer = null;
                     }
+                    unregisterReceivers();
                     synchronized (BluetoothOppService.this) {
                         if (mUpdateThread != null) {
                             try {
@@ -249,6 +339,7 @@
                             mUpdateThread = null;
                         }
                     }
+                    mNotifier.cancelNotifications();
                     break;
                 case START_LISTENER:
                     if (mAdapter.isEnabled()) {
@@ -256,14 +347,16 @@
                     }
                     break;
                 case MEDIA_SCANNED:
-                    if (V) Log.v(TAG, "Update mInfo.id " + msg.arg1 + " for data uri= "
+                    if (V) {
+                        Log.v(TAG, "Update mInfo.id " + msg.arg1 + " for data uri= "
                                 + msg.obj.toString());
+                    }
                     ContentValues updateValues = new ContentValues();
                     Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + msg.arg1);
                     updateValues.put(Constants.MEDIA_SCANNED, Constants.MEDIA_SCANNED_SCANNED_OK);
                     updateValues.put(BluetoothShare.URI, msg.obj.toString()); // update
-                    updateValues.put(BluetoothShare.MIMETYPE, getContentResolver().getType(
-                            Uri.parse(msg.obj.toString())));
+                    updateValues.put(BluetoothShare.MIMETYPE,
+                            getContentResolver().getType(Uri.parse(msg.obj.toString())));
                     getContentResolver().update(contentUri, updateValues, null, null);
                     synchronized (BluetoothOppService.this) {
                         mMediaScanInProgress = false;
@@ -281,8 +374,10 @@
                     }
                     break;
                 case MSG_INCOMING_BTOPP_CONNECTION:
-                    if (D) Log.d(TAG, "Get incoming connection");
-                    ObexTransport transport = (ObexTransport)msg.obj;
+                    if (D) {
+                        Log.d(TAG, "Get incoming connection");
+                    }
+                    ObexTransport transport = (ObexTransport) msg.obj;
 
                     /*
                      * Strategy for incoming connections:
@@ -290,7 +385,7 @@
                      * 2. If there is ongoing transfer, hold it for 20 seconds(1 seconds * 20 times)
                      * 3. If there is on-hold connection, reject directly
                      */
-                    if (mBatchs.size() == 0 && mPendingConnection == null) {
+                    if (mBatches.size() == 0 && mPendingConnection == null) {
                         Log.i(TAG, "Start Obex Server");
                         createServerSession(transport);
                     } else {
@@ -301,9 +396,6 @@
                             } catch (IOException e) {
                                 Log.e(TAG, "close tranport error");
                             }
-                        } else if (Constants.USE_TCP_DEBUG && !Constants.USE_TCP_SIMPLE_SERVER) {
-                            Log.i(TAG, "Start Obex Server in TCP DEBUG mode");
-                            createServerSession(transport);
                         } else {
                             Log.i(TAG, "OPP busy! Retry after 1 second");
                             mIncomingRetries = mIncomingRetries + 1;
@@ -315,7 +407,7 @@
                     }
                     break;
                 case MSG_INCOMING_CONNECTION_RETRY:
-                    if (mBatchs.size() == 0) {
+                    if (mBatches.size() == 0) {
                         Log.i(TAG, "Start Obex Server");
                         createServerSession(mPendingConnection);
                         mIncomingRetries = 0;
@@ -329,7 +421,7 @@
                                 Log.e(TAG, "close tranport error");
                             }
                             if (mServerSocket != null) {
-                                mServerSocket.prepareForNewConnect();
+                                acceptNewConnections();
                             }
                             mIncomingRetries = 0;
                             mPendingConnection = null;
@@ -347,30 +439,36 @@
     };
 
     private ObexServerSockets mServerSocket;
+
     private void startSocketListener() {
-        if (D) Log.d(TAG, "start Socket Listeners");
+        if (D) {
+            Log.d(TAG, "start Socket Listeners");
+        }
         stopListeners();
         mServerSocket = ObexServerSockets.createInsecure(this);
+        acceptNewConnections();
         SdpManager sdpManager = SdpManager.getDefaultManager();
         if (sdpManager == null || mServerSocket == null) {
             Log.e(TAG, "ERROR:serversocket object is NULL  sdp manager :" + sdpManager
-                            + " mServerSocket:" + mServerSocket);
+                    + " mServerSocket:" + mServerSocket);
             return;
         }
         mOppSdpHandle =
                 sdpManager.createOppOpsRecord("OBEX Object Push", mServerSocket.getRfcommChannel(),
                         mServerSocket.getL2capPsm(), 0x0102, SUPPORTED_OPP_FORMAT);
-        if (D) Log.d(TAG, "mOppSdpHandle :" + mOppSdpHandle);
+        if (D) {
+            Log.d(TAG, "mOppSdpHandle :" + mOppSdpHandle);
+        }
     }
 
     @Override
-    public boolean cleanup() {
-        if (V) Log.v(TAG, "onDestroy");
-        getContentResolver().unregisterContentObserver(mObserver);
-        unregisterReceiver(mBluetoothReceiver);
+    protected void cleanup() {
+        if (V) {
+            Log.v(TAG, "onDestroy");
+        }
         stopListeners();
-        if (mBatchs != null) {
-            mBatchs.clear();
+        if (mBatches != null) {
+            mBatches.clear();
         }
         if (mShares != null) {
             mShares.clear();
@@ -378,15 +476,28 @@
         if (mHandler != null) {
             mHandler.removeCallbacksAndMessages(null);
         }
-        return true;
+    }
+
+    private void unregisterReceivers() {
+        try {
+            if (mObserver != null) {
+                getContentResolver().unregisterContentObserver(mObserver);
+                mObserver = null;
+            }
+            unregisterReceiver(mBluetoothReceiver);
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "unregisterReceivers " + e.toString());
+        }
     }
 
     /* suppose we auto accept an incoming OPUSH connection */
     private void createServerSession(ObexTransport transport) {
-        mServerSession = new BluetoothOppObexServerSession(this, transport, mServerSocket);
+        mServerSession = new BluetoothOppObexServerSession(this, transport, this);
         mServerSession.preStart();
-        if (D) Log.d(TAG, "Get ServerSession " + mServerSession.toString()
-                    + " for incoming connection" + transport.toString());
+        if (D) {
+            Log.d(TAG, "Get ServerSession " + mServerSession.toString() + " for incoming connection"
+                    + transport.toString());
+        }
     }
 
     private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@@ -397,7 +508,9 @@
             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                 switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
                     case BluetoothAdapter.STATE_ON:
-                        if (V) Log.v(TAG, "Bluetooth state changed: STATE_ON");
+                        if (V) {
+                            Log.v(TAG, "Bluetooth state changed: STATE_ON");
+                        }
                         startListener();
                         // If this is within a sending process, continue the handle
                         // logic to display device picker dialog.
@@ -422,7 +535,9 @@
 
                         break;
                     case BluetoothAdapter.STATE_TURNING_OFF:
-                        if (V) Log.v(TAG, "Bluetooth state changed: STATE_TURNING_OFF");
+                        if (V) {
+                            Log.v(TAG, "Bluetooth state changed: STATE_TURNING_OFF");
+                        }
                         mHandler.sendMessage(mHandler.obtainMessage(STOP_LISTENER));
                         break;
                 }
@@ -441,16 +556,19 @@
     }
 
     private class UpdateThread extends Thread {
-        private boolean isInterrupted ;
-        public UpdateThread() {
+        private boolean mIsInterrupted;
+
+        UpdateThread() {
             super("Bluetooth Share Service");
-            isInterrupted = false;
+            mIsInterrupted = false;
         }
 
         @Override
         public void interrupt() {
-            isInterrupted = true;
-            if (D) Log.d(TAG, "Interrupted :" + isInterrupted);
+            mIsInterrupted = true;
+            if (D) {
+                Log.d(TAG, "OPP UpdateThread interrupted ");
+            }
             super.interrupt();
         }
 
@@ -459,24 +577,25 @@
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 
-            boolean keepService = false;
-            while (!isInterrupted) {
+            while (!mIsInterrupted) {
                 synchronized (BluetoothOppService.this) {
                     if (mUpdateThread != this) {
                         throw new IllegalStateException(
                                 "multiple UpdateThreads in BluetoothOppService");
                     }
-                    if (V) Log.v(TAG, "pendingUpdate is " + mPendingUpdate + " keepUpdateThread is "
-                                + keepService + " sListenStarted is " + mListenStarted +
-                                " isInterrupted :" + isInterrupted );
+                    if (V) {
+                        Log.v(TAG, "pendingUpdate is " + mPendingUpdate + " sListenStarted is "
+                                + mListenStarted + " isInterrupted :" + mIsInterrupted);
+                    }
                     if (!mPendingUpdate) {
                         mUpdateThread = null;
                         return;
                     }
                     mPendingUpdate = false;
                 }
-                Cursor cursor = getContentResolver().query(BluetoothShare.CONTENT_URI, null, null,
-                        null, BluetoothShare._ID);
+                Cursor cursor =
+                        getContentResolver().query(BluetoothShare.CONTENT_URI, null, null, null,
+                                BluetoothShare._ID);
 
                 if (cursor == null) {
                     return;
@@ -486,7 +605,6 @@
 
                 int arrayPos = 0;
 
-                keepService = false;
                 boolean isAfterLast = cursor.isAfterLast();
 
                 int idColumn = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
@@ -507,15 +625,17 @@
                  */
                 while (!isAfterLast || arrayPos < mShares.size() && mListenStarted) {
                     if (isAfterLast) {
-                        // We're beyond the end of the cursor but there's still
-                        // some
+                        // We're beyond the end of the cursor but there's still some
                         // stuff in the local array, which can only be junk
-                        if (mShares.size() != 0)
-                            if (V) Log.v(TAG, "Array update: trimming " +
-                                mShares.get(arrayPos).mId + " @ " + arrayPos);
+                        if (mShares.size() != 0) {
+                            if (V) {
+                                Log.v(TAG, "Array update: trimming " + mShares.get(arrayPos).mId
+                                        + " @ " + arrayPos);
+                            }
+                        }
 
                         if (shouldScanFile(arrayPos)) {
-                            scanFile(null, arrayPos);
+                            scanFile(arrayPos);
                         }
                         deleteShare(arrayPos); // this advances in the array
                     } else {
@@ -523,45 +643,30 @@
 
                         if (arrayPos == mShares.size()) {
                             insertShare(cursor, arrayPos);
-                            if (V) Log.v(TAG, "Array update: inserting " + id + " @ " + arrayPos);
-                            if (shouldScanFile(arrayPos) && (!scanFile(cursor, arrayPos))) {
-                                keepService = true;
+                            if (V) {
+                                Log.v(TAG, "Array update: inserting " + id + " @ " + arrayPos);
                             }
-                            if (visibleNotification(arrayPos)) {
-                                keepService = true;
-                            }
-                            if (needAction(arrayPos)) {
-                                keepService = true;
-                            }
-
                             ++arrayPos;
                             cursor.moveToNext();
                             isAfterLast = cursor.isAfterLast();
                         } else {
                             int arrayId = 0;
-                            if (mShares.size() != 0)
+                            if (mShares.size() != 0) {
                                 arrayId = mShares.get(arrayPos).mId;
+                            }
 
                             if (arrayId < id) {
-                                if (V) Log.v(TAG, "Array update: removing " + arrayId + " @ "
-                                            + arrayPos);
+                                if (V) {
+                                    Log.v(TAG,
+                                            "Array update: removing " + arrayId + " @ " + arrayPos);
+                                }
                                 if (shouldScanFile(arrayPos)) {
-                                    scanFile(null, arrayPos);
+                                    scanFile(arrayPos);
                                 }
                                 deleteShare(arrayPos);
                             } else if (arrayId == id) {
-                                // This cursor row already exists in the stored
-                                // array
-                                updateShare(cursor, arrayPos, userAccepted);
-                                if (shouldScanFile(arrayPos) && (!scanFile(cursor, arrayPos))) {
-                                    keepService = true;
-                                }
-                                if (visibleNotification(arrayPos)) {
-                                    keepService = true;
-                                }
-                                if (needAction(arrayPos)) {
-                                    keepService = true;
-                                }
+                                // This cursor row already exists in the stored array.
+                                updateShare(cursor, arrayPos);
 
                                 ++arrayPos;
                                 cursor.moveToNext();
@@ -569,18 +674,11 @@
                             } else {
                                 // This cursor entry didn't exist in the stored
                                 // array
-                                if (V) Log.v(TAG, "Array update: appending " + id + " @ " + arrayPos);
+                                if (V) {
+                                    Log.v(TAG, "Array update: appending " + id + " @ " + arrayPos);
+                                }
                                 insertShare(cursor, arrayPos);
 
-                                if (shouldScanFile(arrayPos) && (!scanFile(cursor, arrayPos))) {
-                                    keepService = true;
-                                }
-                                if (visibleNotification(arrayPos)) {
-                                    keepService = true;
-                                }
-                                if (needAction(arrayPos)) {
-                                    keepService = true;
-                                }
                                 ++arrayPos;
                                 cursor.moveToNext();
                                 isAfterLast = cursor.isAfterLast();
@@ -594,7 +692,6 @@
                 cursor.close();
             }
         }
-
     }
 
     private void insertShare(Cursor cursor, int arrayPos) {
@@ -608,8 +705,7 @@
             Log.e(TAG, "insertShare found null URI at cursor!");
         }
         BluetoothOppShareInfo info = new BluetoothOppShareInfo(
-                cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)),
-                uri,
+                cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)), uri,
                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)),
                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA)),
                 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.MIMETYPE)),
@@ -621,7 +717,8 @@
                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)),
                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)),
                 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) != Constants.MEDIA_SCANNED_NOT_SCANNED);
+                cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED))
+                        != Constants.MEDIA_SCANNED_NOT_SCANNED);
 
         if (V) {
             Log.v(TAG, "Service adding new entry");
@@ -661,8 +758,8 @@
         if (info.isReadyToStart()) {
             if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                 /* check if the file exists */
-                BluetoothOppSendFileInfo sendFileInfo = BluetoothOppUtility.getSendFileInfo(
-                        info.mUri);
+                BluetoothOppSendFileInfo sendFileInfo =
+                        BluetoothOppUtility.getSendFileInfo(info.mUri);
                 if (sendFileInfo == null || sendFileInfo.mInputStream == null) {
                     Log.e(TAG, "Can't open file for OUTBOUND info " + info.mId);
                     Constants.updateShareStatus(this, info.mId, BluetoothShare.STATUS_BAD_REQUEST);
@@ -670,70 +767,72 @@
                     return;
                 }
             }
-            if (mBatchs.size() == 0) {
+            if (mBatches.size() == 0) {
                 BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
                 newBatch.mId = mBatchId;
                 mBatchId++;
-                mBatchs.add(newBatch);
+                mBatches.add(newBatch);
                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
-                    if (V) Log.v(TAG, "Service create new Batch " + newBatch.mId
-                                + " for OUTBOUND info " + info.mId);
-                    mTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch);
+                    if (V) {
+                        Log.v(TAG,
+                                "Service create new Batch " + newBatch.mId + " for OUTBOUND info "
+                                        + info.mId);
+                    }
+                    mTransfer = new BluetoothOppTransfer(this, newBatch);
                 } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
-                    if (V) Log.v(TAG, "Service create new Batch " + newBatch.mId
-                                + " for INBOUND info " + info.mId);
-                    mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch,
-                            mServerSession);
+                    if (V) {
+                        Log.v(TAG, "Service create new Batch " + newBatch.mId + " for INBOUND info "
+                                + info.mId);
+                    }
+                    mServerTransfer = new BluetoothOppTransfer(this, newBatch, mServerSession);
                 }
 
                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND && mTransfer != null) {
-                    if (V) Log.v(TAG, "Service start transfer new Batch " + newBatch.mId
-                                + " for info " + info.mId);
+                    if (V) {
+                        Log.v(TAG, "Service start transfer new Batch " + newBatch.mId + " for info "
+                                + info.mId);
+                    }
                     mTransfer.start();
                 } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND
                         && mServerTransfer != null) {
-                    if (V) Log.v(TAG, "Service start server transfer new Batch " + newBatch.mId
+                    if (V) {
+                        Log.v(TAG, "Service start server transfer new Batch " + newBatch.mId
                                 + " for info " + info.mId);
+                    }
                     mServerTransfer.start();
                 }
 
             } else {
                 int i = findBatchWithTimeStamp(info.mTimestamp);
                 if (i != -1) {
-                    if (V) Log.v(TAG, "Service add info " + info.mId + " to existing batch "
-                                + mBatchs.get(i).mId);
-                    mBatchs.get(i).addShare(info);
+                    if (V) {
+                        Log.v(TAG, "Service add info " + info.mId + " to existing batch " + mBatches
+                                .get(i).mId);
+                    }
+                    mBatches.get(i).addShare(info);
                 } else {
                     // There is ongoing batch
                     BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info);
                     newBatch.mId = mBatchId;
                     mBatchId++;
-                    mBatchs.add(newBatch);
-                    if (V) Log.v(TAG, "Service add new Batch " + newBatch.mId + " for info " +
-                            info.mId);
-                    if (Constants.USE_TCP_DEBUG && !Constants.USE_TCP_SIMPLE_SERVER) {
-                        // only allow  concurrent serverTransfer in debug mode
-                        if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
-                            if (V) Log.v(TAG, "TCP_DEBUG start server transfer new Batch " +
-                                    newBatch.mId + " for info " + info.mId);
-                            mServerTransfer = new BluetoothOppTransfer(this, mPowerManager,
-                                    newBatch, mServerSession);
-                            mServerTransfer.start();
-                        }
+                    mBatches.add(newBatch);
+                    if (V) {
+                        Log.v(TAG,
+                                "Service add new Batch " + newBatch.mId + " for info " + info.mId);
                     }
                 }
             }
         }
     }
 
-    private void updateShare(Cursor cursor, int arrayPos, boolean userAccepted) {
+    private void updateShare(Cursor cursor, int arrayPos) {
         BluetoothOppShareInfo info = mShares.get(arrayPos);
         int statusColumn = cursor.getColumnIndexOrThrow(BluetoothShare.STATUS);
 
         info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
         if (info.mUri != null) {
-            info.mUri = Uri.parse(stringFromCursor(info.mUri.toString(), cursor,
-                    BluetoothShare.URI));
+            info.mUri =
+                    Uri.parse(stringFromCursor(info.mUri.toString(), cursor, BluetoothShare.URI));
         } else {
             Log.w(TAG, "updateShare() called for ID " + info.mId + " with null URI");
         }
@@ -745,12 +844,13 @@
         int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.VISIBILITY));
 
         boolean confirmUpdated = false;
-        int newConfirm = cursor.getInt(cursor
-                .getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
+        int newConfirm =
+                cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
 
         if (info.mVisibility == BluetoothShare.VISIBILITY_VISIBLE
-                && newVisibility != BluetoothShare.VISIBILITY_VISIBLE
-                && (BluetoothShare.isStatusCompleted(info.mStatus) || newConfirm == BluetoothShare.USER_CONFIRMATION_PENDING)) {
+                && newVisibility != BluetoothShare.VISIBILITY_VISIBLE && (
+                BluetoothShare.isStatusCompleted(info.mStatus)
+                        || newConfirm == BluetoothShare.USER_CONFIRMATION_PENDING)) {
             mNotifier.mNotificationMgr.cancel(info.mId);
         }
 
@@ -760,8 +860,8 @@
                 && newConfirm != BluetoothShare.USER_CONFIRMATION_PENDING) {
             confirmUpdated = true;
         }
-        info.mConfirm = cursor.getInt(cursor
-                .getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
+        info.mConfirm =
+                cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
         int newStatus = cursor.getInt(statusColumn);
 
         if (BluetoothShare.isStatusCompleted(info.mStatus)) {
@@ -770,17 +870,20 @@
 
         info.mStatus = newStatus;
         info.mTotalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
-        info.mCurrentBytes = cursor.getLong(cursor
-                .getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
+        info.mCurrentBytes =
+                cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
         info.mTimestamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
-        info.mMediaScanned = (cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) != Constants.MEDIA_SCANNED_NOT_SCANNED);
+        info.mMediaScanned = (cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED))
+                != Constants.MEDIA_SCANNED_NOT_SCANNED);
 
         if (confirmUpdated) {
-            if (V) Log.v(TAG, "Service handle info " + info.mId + " confirmation updated");
+            if (V) {
+                Log.v(TAG, "Service handle info " + info.mId + " confirmation updated");
+            }
             /* Inbounds transfer user confirmation status changed, update the session server */
             int i = findBatchWithTimeStamp(info.mTimestamp);
             if (i != -1) {
-                BluetoothOppBatch batch = mBatchs.get(i);
+                BluetoothOppBatch batch = mBatches.get(i);
                 if (mServerTransfer != null && batch.mId == mServerTransfer.getBatchId()) {
                     mServerTransfer.confirmStatusChanged();
                 } //TODO need to think about else
@@ -788,10 +891,12 @@
         }
         int i = findBatchWithTimeStamp(info.mTimestamp);
         if (i != -1) {
-            BluetoothOppBatch batch = mBatchs.get(i);
+            BluetoothOppBatch batch = mBatches.get(i);
             if (batch.mStatus == Constants.BATCH_STATUS_FINISHED
                     || batch.mStatus == Constants.BATCH_STATUS_FAILED) {
-                if (V) Log.v(TAG, "Batch " + batch.mId + " is finished");
+                if (V) {
+                    Log.v(TAG, "Batch " + batch.mId + " is finished");
+                }
                 if (batch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                     if (mTransfer == null) {
                         Log.e(TAG, "Unexpected error! mTransfer is null");
@@ -833,13 +938,17 @@
          */
         int i = findBatchWithTimeStamp(info.mTimestamp);
         if (i != -1) {
-            BluetoothOppBatch batch = mBatchs.get(i);
+            BluetoothOppBatch batch = mBatches.get(i);
             if (batch.hasShare(info)) {
-                if (V) Log.v(TAG, "Service cancel batch for share " + info.mId);
+                if (V) {
+                    Log.v(TAG, "Service cancel batch for share " + info.mId);
+                }
                 batch.cancelBatch();
             }
             if (batch.isEmpty()) {
-                if (V) Log.v(TAG, "Service remove batch  " + batch.mId);
+                if (V) {
+                    Log.v(TAG, "Service remove batch  " + batch.mId);
+                }
                 removeBatch(batch);
             }
         }
@@ -874,8 +983,8 @@
     }
 
     private int findBatchWithTimeStamp(long timestamp) {
-        for (int i = mBatchs.size() - 1; i >= 0; i--) {
-            if (mBatchs.get(i).mTimestamp == timestamp) {
+        for (int i = mBatches.size() - 1; i >= 0; i--) {
+            if (mBatches.get(i).mTimestamp == timestamp) {
                 return i;
             }
         }
@@ -883,33 +992,36 @@
     }
 
     private void removeBatch(BluetoothOppBatch batch) {
-        if (V) Log.v(TAG, "Remove batch " + batch.mId);
-        mBatchs.remove(batch);
-        BluetoothOppBatch nextBatch;
-        if (mBatchs.size() > 0) {
-            for (int i = 0; i < mBatchs.size(); i++) {
+        if (V) {
+            Log.v(TAG, "Remove batch " + batch.mId);
+        }
+        mBatches.remove(batch);
+        if (mBatches.size() > 0) {
+            for (BluetoothOppBatch nextBatch : mBatches) {
                 // we have a running batch
-                nextBatch = mBatchs.get(i);
                 if (nextBatch.mStatus == Constants.BATCH_STATUS_RUNNING) {
                     return;
                 } else {
                     // just finish a transfer, start pending outbound transfer
                     if (nextBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
-                        if (V) Log.v(TAG, "Start pending outbound batch " + nextBatch.mId);
-                        mTransfer = new BluetoothOppTransfer(this, mPowerManager, nextBatch);
+                        if (V) {
+                            Log.v(TAG, "Start pending outbound batch " + nextBatch.mId);
+                        }
+                        mTransfer = new BluetoothOppTransfer(this, nextBatch);
                         mTransfer.start();
                         return;
                     } else if (nextBatch.mDirection == BluetoothShare.DIRECTION_INBOUND
                             && mServerSession != null) {
                         // have to support pending inbound transfer
                         // if an outbound transfer and incoming socket happens together
-                        if (V) Log.v(TAG, "Start pending inbound batch " + nextBatch.mId);
-                        mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, nextBatch,
-                                                                   mServerSession);
+                        if (V) {
+                            Log.v(TAG, "Start pending inbound batch " + nextBatch.mId);
+                        }
+                        mServerTransfer = new BluetoothOppTransfer(this, nextBatch, mServerSession);
                         mServerTransfer.start();
                         if (nextBatch.getPendingShare() != null
-                            && nextBatch.getPendingShare().mConfirm ==
-                                BluetoothShare.USER_CONFIRMATION_CONFIRMED) {
+                                && nextBatch.getPendingShare().mConfirm
+                                == BluetoothShare.USER_CONFIRMATION_CONFIRMED) {
                             mServerTransfer.confirmStatusChanged();
                         }
                         return;
@@ -919,23 +1031,12 @@
         }
     }
 
-    private boolean needAction(int arrayPos) {
-        BluetoothOppShareInfo info = mShares.get(arrayPos);
-        if (BluetoothShare.isStatusCompleted(info.mStatus)) {
-            return false;
-        }
-        return true;
-    }
-
-    private boolean visibleNotification(int arrayPos) {
-        BluetoothOppShareInfo info = mShares.get(arrayPos);
-        return info.hasCompletionNotification();
-    }
-
-    private boolean scanFile(Cursor cursor, int arrayPos) {
+    private boolean scanFile(int arrayPos) {
         BluetoothOppShareInfo info = mShares.get(arrayPos);
         synchronized (BluetoothOppService.this) {
-            if (D) Log.d(TAG, "Scanning file " + info.mFilename);
+            if (D) {
+                Log.d(TAG, "Scanning file " + info.mFilename);
+            }
             if (!mMediaScanInProgress) {
                 mMediaScanInProgress = true;
                 new MediaScannerNotifier(this, info, mHandler);
@@ -949,44 +1050,26 @@
     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;
+                && 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) {
-        final String INVISIBLE = BluetoothShare.VISIBILITY + "=" +
-                BluetoothShare.VISIBILITY_HIDDEN;
+        // remove the invisible/unconfirmed inbound shares
+        int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED,
+                null);
+        if (V) {
+            Log.v(TAG, "Deleted shares, number = " + delNum);
+        }
 
-        // remove the invisible/complete/outbound shares
-        final String WHERE_INVISIBLE_COMPLETE_OUTBOUND = BluetoothShare.DIRECTION + "="
-                + BluetoothShare.DIRECTION_OUTBOUND + " AND " + BluetoothShare.STATUS + ">="
-                + BluetoothShare.STATUS_SUCCESS + " AND " + INVISIBLE;
-        int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI,
-                WHERE_INVISIBLE_COMPLETE_OUTBOUND, null);
-        if (V) Log.v(TAG, "Deleted complete outbound shares, number =  " + delNum);
-
-        // remove the invisible/finished/inbound/failed shares
-        final String WHERE_INVISIBLE_COMPLETE_INBOUND_FAILED = BluetoothShare.DIRECTION + "="
-                + BluetoothShare.DIRECTION_INBOUND + " AND " + BluetoothShare.STATUS + ">"
-                + BluetoothShare.STATUS_SUCCESS + " AND " + INVISIBLE;
-        delNum = contentResolver.delete(BluetoothShare.CONTENT_URI,
-                WHERE_INVISIBLE_COMPLETE_INBOUND_FAILED, null);
-        if (V) Log.v(TAG, "Deleted complete inbound failed shares, number = " + delNum);
-
-        // Only keep the inbound and successful shares for LiverFolder use
-        // Keep the latest 1000 to easy db query
-        final String WHERE_INBOUND_SUCCESS = BluetoothShare.DIRECTION + "="
-                + BluetoothShare.DIRECTION_INBOUND + " AND " + BluetoothShare.STATUS + "="
-                + BluetoothShare.STATUS_SUCCESS + " AND " + INVISIBLE;
-        Cursor cursor = contentResolver.query(BluetoothShare.CONTENT_URI, new String[] {
-            BluetoothShare._ID
-        }, WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id
-
+        // Keep the latest inbound and successful shares.
+        Cursor cursor =
+                contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
+                        WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id
         if (cursor == null) {
             return;
         }
-
         int recordNum = cursor.getCount();
         if (recordNum > Constants.MAX_RECORDS_IN_DATABASE) {
             int numToDelete = recordNum - Constants.MAX_RECORDS_IN_DATABASE;
@@ -996,7 +1079,9 @@
                 long id = cursor.getLong(columnId);
                 delNum = contentResolver.delete(BluetoothShare.CONTENT_URI,
                         BluetoothShare._ID + " < " + id, null);
-                if (V) Log.v(TAG, "Deleted old inbound success share: " + delNum);
+                if (V) {
+                    Log.v(TAG, "Deleted old inbound success share: " + delNum);
+                }
             }
         }
         cursor.close();
@@ -1012,20 +1097,26 @@
 
         private Handler mCallback;
 
-        public MediaScannerNotifier(Context context, BluetoothOppShareInfo info, Handler handler) {
+        MediaScannerNotifier(Context context, BluetoothOppShareInfo info, Handler handler) {
             mContext = context;
             mInfo = info;
             mCallback = handler;
             mConnection = new MediaScannerConnection(mContext, this);
-            if (V) Log.v(TAG, "Connecting to MediaScannerConnection ");
+            if (V) {
+                Log.v(TAG, "Connecting to MediaScannerConnection ");
+            }
             mConnection.connect();
         }
 
+        @Override
         public void onMediaScannerConnected() {
-            if (V) Log.v(TAG, "MediaScannerConnection onMediaScannerConnected");
+            if (V) {
+                Log.v(TAG, "MediaScannerConnection onMediaScannerConnected");
+            }
             mConnection.scanFile(mInfo.mFilename, mInfo.mMimetype);
         }
 
+        @Override
         public void onScanCompleted(String path, Uri uri) {
             try {
                 if (V) {
@@ -1047,10 +1138,12 @@
                     msg.arg1 = mInfo.mId;
                     msg.sendToTarget();
                 }
-            } catch (Exception ex) {
+            } catch (NullPointerException ex) {
                 Log.v(TAG, "!!!MediaScannerConnection exception: " + ex);
             } finally {
-                if (V) Log.v(TAG, "MediaScannerConnection disconnect");
+                if (V) {
+                    Log.v(TAG, "MediaScannerConnection disconnect");
+                }
                 mConnection.disconnect();
             }
         }
@@ -1058,7 +1151,9 @@
 
     private void stopListeners() {
         if (mAdapter != null && mOppSdpHandle >= 0 && SdpManager.getDefaultManager() != null) {
-            if (D) Log.d(TAG, "Removing SDP record mOppSdpHandle :" + mOppSdpHandle);
+            if (D) {
+                Log.d(TAG, "Removing SDP record mOppSdpHandle :" + mOppSdpHandle);
+            }
             boolean status = SdpManager.getDefaultManager().removeSdpRecord(mOppSdpHandle);
             Log.d(TAG, "RemoveSDPrecord returns " + status);
             mOppSdpHandle = -1;
@@ -1067,25 +1162,39 @@
             mServerSocket.shutdown(false);
             mServerSocket = null;
         }
-        if (D) Log.d(TAG, "stopListeners   mServerSocket :" + mServerSocket);
+        if (D) {
+            Log.d(TAG, "stopListeners: mServerSocket is null");
+        }
     }
 
     @Override
     public boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
-        if (D) Log.d(TAG, " onConnect BluetoothSocket :" + socket + " \n :device :" + device);
+
+        if (D) {
+            Log.d(TAG, " onConnect BluetoothSocket :" + socket + " \n :device :" + device);
+        }
+        if (!mAcceptNewConnections) {
+            Log.d(TAG, " onConnect BluetoothSocket :" + socket + " rejected");
+            return false;
+        }
         BluetoothObexTransport transport = new BluetoothObexTransport(socket);
-        Message msg = Message.obtain();
-        msg.setTarget(mHandler);
-        msg.what = MSG_INCOMING_BTOPP_CONNECTION;
+        Message msg = mHandler.obtainMessage(MSG_INCOMING_BTOPP_CONNECTION);
         msg.obj = transport;
         msg.sendToTarget();
+        mAcceptNewConnections = false;
         return true;
     }
 
     @Override
     public void onAcceptFailed() {
-        // TODO Auto-generated method stub
         Log.d(TAG, " onAcceptFailed:");
         mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER));
     }
+
+    /**
+     * Set mAcceptNewConnections to true to allow new connections.
+     */
+    void acceptNewConnections() {
+        mAcceptNewConnections = true;
+    }
 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index b3e4c98..91af9ca 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -32,15 +32,12 @@
 
 package com.android.bluetooth.opp;
 
-import javax.obex.ObexTransport;
-
-import com.android.bluetooth.BluetoothObexTransport;
-
 import android.app.NotificationManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
+import android.bluetooth.SdpOppOpsRecord;
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -51,17 +48,16 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
+import android.os.ParcelUuid;
 import android.os.Process;
 import android.util.Log;
-import android.os.ParcelUuid;
-import android.bluetooth.SdpOppOpsRecord;
+
+import com.android.bluetooth.BluetoothObexTransport;
 
 import java.io.File;
 import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
+
+import javax.obex.ObexTransport;
 
 /**
  * This class run an actual Opp transfer session (from connect target device to
@@ -110,25 +106,27 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (D) Log.d(TAG, " Action :" + action);
+            if (D) {
+                Log.d(TAG, " Action :" + action);
+            }
             if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                 if (device == null || mBatch == null || mCurrentShare == null) {
                     Log.e(TAG, "device : " + device + " mBatch :" + mBatch + " mCurrentShare :"
-                                    + mCurrentShare);
+                            + mCurrentShare);
                     return;
                 }
                 try {
-                    if (V)
+                    if (V) {
                         Log.v(TAG, "Device :" + device + "- OPP device: " + mBatch.mDestination
-                                        + " \n mCurrentShare.mConfirm == "
-                                        + mCurrentShare.mConfirm);
-                    if ((device.equals(mBatch.mDestination))
-                            && (mCurrentShare.mConfirm
-                                       == BluetoothShare.USER_CONFIRMATION_PENDING)) {
-                        if (V)
+                                + " \n mCurrentShare.mConfirm == " + mCurrentShare.mConfirm);
+                    }
+                    if ((device.equals(mBatch.mDestination)) && (mCurrentShare.mConfirm
+                            == BluetoothShare.USER_CONFIRMATION_PENDING)) {
+                        if (V) {
                             Log.v(TAG, "ACTION_ACL_DISCONNECTED to be processed for batch: "
-                                            + mBatch.mId);
+                                    + mBatch.mId);
+                        }
                         // Remove the timeout message triggered earlier during Obex Put
                         mSessionHandler.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
                         // Now reuse the same message to clean up the session.
@@ -161,6 +159,7 @@
                             intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
                     if (record == null) {
                         Log.w(TAG, " Invalid SDP , ignoring !!");
+                        markConnectionFailed(null);
                         return;
                     }
                     mConnectThread =
@@ -170,12 +169,12 @@
                 }
             }
         }
-    };
+    }
 
     private OppConnectionReceiver mBluetoothReceiver;
 
-    public BluetoothOppTransfer(Context context, PowerManager powerManager,
-            BluetoothOppBatch batch, BluetoothOppObexSession session) {
+    public BluetoothOppTransfer(Context context, BluetoothOppBatch batch,
+            BluetoothOppObexSession session) {
 
         mContext = context;
         mBatch = batch;
@@ -186,8 +185,8 @@
 
     }
 
-    public BluetoothOppTransfer(Context context, PowerManager powerManager, BluetoothOppBatch batch) {
-        this(context, powerManager, batch, null);
+    public BluetoothOppTransfer(Context context, BluetoothOppBatch batch) {
+        this(context, batch, null);
     }
 
     public int getBatchId() {
@@ -198,7 +197,7 @@
      * Receives events from mConnectThread & mSession back in the main thread.
      */
     private class EventHandler extends Handler {
-        public EventHandler(Looper looper) {
+        EventHandler(Looper looper) {
             super(looper);
         }
 
@@ -206,8 +205,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case SOCKET_ERROR_RETRY:
-                    mConnectThread = new
-                        SocketConnectThread((BluetoothDevice)msg.obj, true);
+                    mConnectThread = new SocketConnectThread((BluetoothDevice) msg.obj, true);
 
                     mConnectThread.start();
                     break;
@@ -216,7 +214,9 @@
                     * RFCOMM connect fail is for outbound share only! Mark batch
                     * failed, and all shares in batch failed
                     */
-                    if (V) Log.v(TAG, "receive TRANSPORT_ERROR msg");
+                    if (V) {
+                        Log.v(TAG, "receive TRANSPORT_ERROR msg");
+                    }
                     mConnectThread = null;
                     markBatchFailed(BluetoothShare.STATUS_CONNECTION_ERROR);
                     mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
@@ -227,9 +227,11 @@
                     * RFCOMM connected is for outbound share only! Create
                     * BluetoothOppObexClientSession and start it
                     */
-                    if (V) Log.v(TAG, "Transfer receive TRANSPORT_CONNECTED msg");
+                    if (V) {
+                        Log.v(TAG, "Transfer receive TRANSPORT_CONNECTED msg");
+                    }
                     mConnectThread = null;
-                    mTransport = (ObexTransport)msg.obj;
+                    mTransport = (ObexTransport) msg.obj;
                     startObexSession();
 
                     break;
@@ -241,19 +243,25 @@
                     * For inbounds session, do nothing. If there is next file to receive,it
                     * will be notified through onShareAdded()
                     */
-                    BluetoothOppShareInfo info = (BluetoothOppShareInfo)msg.obj;
-                    if (V) Log.v(TAG, "receive MSG_SHARE_COMPLETE for info " + info.mId);
+                    BluetoothOppShareInfo info = (BluetoothOppShareInfo) msg.obj;
+                    if (V) {
+                        Log.v(TAG, "receive MSG_SHARE_COMPLETE for info " + info.mId);
+                    }
                     if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                         mCurrentShare = mBatch.getPendingShare();
 
                         if (mCurrentShare != null) {
                             /* we have additional share to process */
-                            if (V) Log.v(TAG, "continue session for info " + mCurrentShare.mId +
-                                    " from batch " + mBatch.mId);
+                            if (V) {
+                                Log.v(TAG, "continue session for info " + mCurrentShare.mId
+                                        + " from batch " + mBatch.mId);
+                            }
                             processCurrentShare();
                         } else {
                             /* for outbound transfer, all shares are processed */
-                            if (V) Log.v(TAG, "Batch " + mBatch.mId + " is done");
+                            if (V) {
+                                Log.v(TAG, "Batch " + mBatch.mId + " is done");
+                            }
                             mSession.stop();
                         }
                     }
@@ -264,8 +272,10 @@
                     * finished
                     */
                     cleanUp();
-                    BluetoothOppShareInfo info1 = (BluetoothOppShareInfo)msg.obj;
-                    if (V) Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId);
+                    BluetoothOppShareInfo info1 = (BluetoothOppShareInfo) msg.obj;
+                    if (V) {
+                        Log.v(TAG, "receive MSG_SESSION_COMPLETE for batch " + mBatch.mId);
+                    }
                     mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
                     /*
                      * trigger content provider again to know batch status change
@@ -275,7 +285,9 @@
 
                 case BluetoothOppObexSession.MSG_SESSION_ERROR:
                     /* Handle the error state of an Obex session */
-                    if (V) Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId);
+                    if (V) {
+                        Log.v(TAG, "receive MSG_SESSION_ERROR for batch " + mBatch.mId);
+                    }
                     cleanUp();
                     try {
                         BluetoothOppShareInfo info2 = (BluetoothOppShareInfo) msg.obj;
@@ -292,8 +304,10 @@
                     break;
 
                 case BluetoothOppObexSession.MSG_SHARE_INTERRUPTED:
-                    if (V) Log.v(TAG, "receive MSG_SHARE_INTERRUPTED for batch " + mBatch.mId);
-                    BluetoothOppShareInfo info3 = (BluetoothOppShareInfo)msg.obj;
+                    if (V) {
+                        Log.v(TAG, "receive MSG_SHARE_INTERRUPTED for batch " + mBatch.mId);
+                    }
+                    BluetoothOppShareInfo info3 = (BluetoothOppShareInfo) msg.obj;
                     if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
                         try {
                             if (mTransport == null) {
@@ -304,19 +318,23 @@
                         } catch (IOException e) {
                             Log.e(TAG, "failed to close mTransport");
                         }
-                        if (V) Log.v(TAG, "mTransport closed ");
+                        if (V) {
+                            Log.v(TAG, "mTransport closed ");
+                        }
                         mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
                         if (info3 != null) {
                             markBatchFailed(info3.mStatus);
                         } else {
-                            markBatchFailed();
+                            markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
                         }
                         tickShareStatus(mCurrentShare);
                     }
                     break;
 
                 case BluetoothOppObexSession.MSG_CONNECT_TIMEOUT:
-                    if (V) Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId);
+                    if (V) {
+                        Log.v(TAG, "receive MSG_CONNECT_TIMEOUT for batch " + mBatch.mId);
+                    }
                     /* for outbound transfer, the block point is BluetoothSocket.write()
                      * The only way to unblock is to tear down lower transport
                      * */
@@ -330,7 +348,9 @@
                         } catch (IOException e) {
                             Log.e(TAG, "failed to close mTransport");
                         }
-                        if (V) Log.v(TAG, "mTransport closed ");
+                        if (V) {
+                            Log.v(TAG, "mTransport closed ");
+                        }
                     } else {
                         /*
                          * For inbound transfer, the block point is waiting for
@@ -338,8 +358,8 @@
                          */
 
                         // Remove incoming file confirm notification
-                        NotificationManager nm = (NotificationManager)mContext
-                                .getSystemService(Context.NOTIFICATION_SERVICE);
+                        NotificationManager nm = (NotificationManager) mContext.getSystemService(
+                                Context.NOTIFICATION_SERVICE);
                         nm.cancel(mCurrentShare.mId);
                         // Send intent to UI for timeout handling
                         Intent in = new Intent(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION);
@@ -355,8 +375,8 @@
     private void markShareTimeout(BluetoothOppShareInfo share) {
         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
         ContentValues updateValues = new ContentValues();
-        updateValues
-                .put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_TIMEOUT);
+        updateValues.put(BluetoothShare.USER_CONFIRMATION,
+                BluetoothShare.USER_CONFIRMATION_TIMEOUT);
         mContext.getContentResolver().update(contentUri, updateValues, null, null);
     }
 
@@ -365,13 +385,19 @@
             try {
                 wait(1000);
             } catch (InterruptedException e) {
-                if (V) Log.v(TAG, "Interrupted waiting for markBatchFailed");
+                if (V) {
+                    Log.v(TAG, "Interrupted waiting for markBatchFailed");
+                }
             }
         }
 
-        if (D) Log.d(TAG, "Mark all ShareInfo in the batch as failed");
+        if (D) {
+            Log.d(TAG, "Mark all ShareInfo in the batch as failed");
+        }
         if (mCurrentShare != null) {
-            if (V) Log.v(TAG, "Current share has status " + mCurrentShare.mStatus);
+            if (V) {
+                Log.v(TAG, "Current share has status " + mCurrentShare.mStatus);
+            }
             if (BluetoothShare.isStatusError(mCurrentShare.mStatus)) {
                 failReason = mCurrentShare.mStatus;
             }
@@ -394,8 +420,8 @@
                 updateValues.put(BluetoothShare.STATUS, info.mStatus);
                 /* Update un-processed outbound transfer to show some info */
                 if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
-                    BluetoothOppSendFileInfo fileInfo
-                            = BluetoothOppUtility.getSendFileInfo(info.mUri);
+                    BluetoothOppSendFileInfo fileInfo =
+                            BluetoothOppUtility.getSendFileInfo(info.mUri);
                     BluetoothOppUtility.closeSendFileInfo(info.mUri);
                     if (fileInfo.mFileName != null) {
                         updateValues.put(BluetoothShare.FILENAME_HINT, fileInfo.mFileName);
@@ -415,10 +441,6 @@
 
     }
 
-    private void markBatchFailed() {
-        markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
-    }
-
     /*
      * NOTE
      * For outbound transfer
@@ -436,6 +458,7 @@
      * 2) Start handler thread
      * 3) Start the session and process the first share in batch
      */
+
     /**
      * Start the transfer
      */
@@ -447,15 +470,17 @@
          */
         if (!mAdapter.isEnabled()) {
             Log.e(TAG, "Can't start transfer when Bluetooth is disabled for " + mBatch.mId);
-            markBatchFailed();
+            markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
             mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
             return;
         }
 
         if (mHandlerThread == null) {
-            if (V) Log.v(TAG, "Create handler thread for batch " + mBatch.mId);
-            mHandlerThread = new HandlerThread("BtOpp Transfer Handler",
-                    Process.THREAD_PRIORITY_BACKGROUND);
+            if (V) {
+                Log.v(TAG, "Create handler thread for batch " + mBatch.mId);
+            }
+            mHandlerThread =
+                    new HandlerThread("BtOpp Transfer Handler", Process.THREAD_PRIORITY_BACKGROUND);
             mHandlerThread.start();
             mSessionHandler = new EventHandler(mHandlerThread.getLooper());
 
@@ -477,22 +502,31 @@
      * Stop the transfer
      */
     public void stop() {
-        if (V) Log.v(TAG, "stop");
+        if (V) {
+            Log.v(TAG, "stop");
+        }
+        if (mSession != null) {
+            if (V) {
+                Log.v(TAG, "Stop mSession");
+            }
+            mSession.stop();
+        }
+
         cleanUp();
         if (mConnectThread != null) {
             try {
                 mConnectThread.interrupt();
-                if (V) Log.v(TAG, "waiting for connect thread to terminate");
+                if (V) {
+                    Log.v(TAG, "waiting for connect thread to terminate");
+                }
                 mConnectThread.join();
             } catch (InterruptedException e) {
-                if (V) Log.v(TAG, "Interrupted waiting for connect thread to join");
+                if (V) {
+                    Log.v(TAG, "Interrupted waiting for connect thread to join");
+                }
             }
             mConnectThread = null;
         }
-        if (mSession != null) {
-            if (V) Log.v(TAG, "Stop mSession");
-            mSession.stop();
-        }
         // Prevent concurrent access
         synchronized (this) {
             if (mHandlerThread != null) {
@@ -515,11 +549,14 @@
             Log.e(TAG, "Unexpected error happened !");
             return;
         }
-        if (V) Log.v(TAG, "Start session for info " + mCurrentShare.mId + " for batch " +
-                mBatch.mId);
+        if (V) {
+            Log.v(TAG, "Start session for info " + mCurrentShare.mId + " for batch " + mBatch.mId);
+        }
 
         if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {
-            if (V) Log.v(TAG, "Create Client session with transport " + mTransport.toString());
+            if (V) {
+                Log.v(TAG, "Create Client session with transport " + mTransport.toString());
+            }
             mSession = new BluetoothOppObexClientSession(mContext, mTransport);
         } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {
             /*
@@ -530,11 +567,13 @@
             if (mSession == null) {
                 /** set current share as error */
                 Log.e(TAG, "Unexpected error happened !");
-                markBatchFailed();
+                markBatchFailed(BluetoothShare.STATUS_UNKNOWN_ERROR);
                 mBatch.mStatus = Constants.BATCH_STATUS_FAILED;
                 return;
             }
-            if (V) Log.v(TAG, "Transfer has Server session" + mSession.toString());
+            if (V) {
+                Log.v(TAG, "Transfer has Server session" + mSession.toString());
+            }
         }
 
         mSession.start(mSessionHandler, mBatch.getNumShares());
@@ -554,7 +593,9 @@
                     filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
                     filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
                     mContext.registerReceiver(mBluetoothReceiver, filter);
-                    if (V) Log.v(TAG, "Registered mBluetoothReceiver");
+                    if (V) {
+                        Log.v(TAG, "Registered mBluetoothReceiver");
+                    }
                 }
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "mBluetoothReceiver Registered already ", e);
@@ -564,7 +605,9 @@
 
     private void processCurrentShare() {
         /* This transfer need user confirm */
-        if (V) Log.v(TAG, "processCurrentShare" + mCurrentShare.mId);
+        if (V) {
+            Log.v(TAG, "processCurrentShare" + mCurrentShare.mId);
+        }
         mSession.addShare(mCurrentShare);
         if (mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) {
             confirmStatusChanged();
@@ -578,6 +621,7 @@
     public void confirmStatusChanged() {
         /* unblock server session */
         final Thread notifyThread = new Thread("Server Unblock thread") {
+            @Override
             public void run() {
                 synchronized (mSession) {
                     mSession.unblock();
@@ -585,14 +629,18 @@
                 }
             }
         };
-        if (V) Log.v(TAG, "confirmStatusChanged to unblock mSession" + mSession.toString());
+        if (V) {
+            Log.v(TAG, "confirmStatusChanged to unblock mSession" + mSession.toString());
+        }
         notifyThread.start();
     }
 
     private void startConnectSession() {
         mDevice = mBatch.mDestination;
         if (!mBatch.mDestination.sdpSearch(BluetoothUuid.ObexObjectPush)) {
-            if (D) Log.d(TAG, "SDP failed, start rfcomm connect directly");
+            if (D) {
+                Log.d(TAG, "SDP failed, start rfcomm connect directly");
+            }
             /* update bd address as sdp could not be started */
             mDevice = null;
             /* SDP failed, start rfcomm connect directly */
@@ -604,56 +652,59 @@
     private SocketConnectThread mConnectThread;
 
     private class SocketConnectThread extends Thread {
-        private final String host;
+        private final String mHost;
 
-        private final BluetoothDevice device;
+        private final BluetoothDevice mDevice;
 
-        private final int channel;
+        private final int mChannel;
 
-        private int l2cChannel = 0;
+        private int mL2cChannel = 0;
 
-        private boolean isConnected;
+        private boolean mIsConnected;
 
-        private long timestamp;
+        private long mTimestamp;
 
-        private BluetoothSocket btSocket = null;
+        private BluetoothSocket mBtSocket = null;
 
         private boolean mRetry = false;
 
         private boolean mSdpInitiated = false;
 
-        private boolean isInterrupted = false;
+        private boolean mIsInterrupted = false;
 
         /* create a Rfcomm/L2CAP Socket */
-        public SocketConnectThread(BluetoothDevice device, boolean retry) {
+        SocketConnectThread(BluetoothDevice device, boolean retry) {
             super("Socket Connect Thread");
-            this.device = device;
-            this.host = null;
-            this.channel = -1;
-            isConnected = false;
+            this.mDevice = device;
+            this.mHost = null;
+            this.mChannel = -1;
+            mIsConnected = false;
             mRetry = retry;
             mSdpInitiated = false;
         }
 
         /* create a Rfcomm/L2CAP Socket */
-        public SocketConnectThread(
-                BluetoothDevice device, boolean retry, boolean sdpInitiated, int l2capChannel) {
+        SocketConnectThread(BluetoothDevice device, boolean retry, boolean sdpInitiated,
+                int l2capChannel) {
             super("Socket Connect Thread");
-            this.device = device;
-            this.host = null;
-            this.channel = -1;
-            isConnected = false;
+            this.mDevice = device;
+            this.mHost = null;
+            this.mChannel = -1;
+            mIsConnected = false;
             mRetry = retry;
             mSdpInitiated = sdpInitiated;
-            l2cChannel = l2capChannel;
+            mL2cChannel = l2capChannel;
         }
 
+        @Override
         public void interrupt() {
-            if (D) Log.d(TAG, "start interrupt :" + btSocket);
-            isInterrupted = true;
-            if (btSocket != null) {
+            if (D) {
+                Log.d(TAG, "start interrupt :" + mBtSocket);
+            }
+            mIsInterrupted = true;
+            if (mBtSocket != null) {
                 try {
-                    btSocket.close();
+                    mBtSocket.close();
                 } catch (IOException e) {
                     Log.v(TAG, "Error when close socket");
                 }
@@ -661,32 +712,38 @@
         }
 
         private void connectRfcommSocket() {
-            if (V) Log.v(TAG, "connectRfcommSocket");
+            if (V) {
+                Log.v(TAG, "connectRfcommSocket");
+            }
             try {
-                if (isInterrupted) {
+                if (mIsInterrupted) {
                     Log.d(TAG, "connectRfcommSocket interrupted");
-                    markConnectionFailed(btSocket);
+                    markConnectionFailed(mBtSocket);
                     return;
                 }
-                btSocket = device.createInsecureRfcommSocketToServiceRecord(
+                mBtSocket = mDevice.createInsecureRfcommSocketToServiceRecord(
                         BluetoothUuid.ObexObjectPush.getUuid());
             } catch (IOException e1) {
                 Log.e(TAG, "Rfcomm socket create error", e1);
-                markConnectionFailed(btSocket);
+                markConnectionFailed(mBtSocket);
                 return;
             }
             try {
-                btSocket.connect();
+                mBtSocket.connect();
 
-                if (V)
-                    Log.v(TAG, "Rfcomm socket connection attempt took "
-                                    + (System.currentTimeMillis() - timestamp) + " ms");
+                if (V) {
+                    Log.v(TAG,
+                            "Rfcomm socket connection attempt took " + (System.currentTimeMillis()
+                                    - mTimestamp) + " ms");
+                }
                 BluetoothObexTransport transport;
-                transport = new BluetoothObexTransport(btSocket);
+                transport = new BluetoothObexTransport(mBtSocket);
 
-                BluetoothOppPreference.getInstance(mContext).setName(device, device.getName());
+                BluetoothOppPreference.getInstance(mContext).setName(mDevice, mDevice.getName());
 
-                if (V) Log.v(TAG, "Send transport message " + transport.toString());
+                if (V) {
+                    Log.v(TAG, "Send transport message " + transport.toString());
+                }
 
                 mSessionHandler.obtainMessage(TRANSPORT_CONNECTED, transport).sendToTarget();
             } catch (IOException e) {
@@ -698,22 +755,25 @@
                 // inform this socket asking it to retry apart from a blind
                 // delayed retry.
                 if (!mRetry && e.getMessage().equals(SOCKET_LINK_KEY_ERROR)) {
-                    Message msg = mSessionHandler.obtainMessage(SOCKET_ERROR_RETRY, -1, -1, device);
+                    Message msg =
+                            mSessionHandler.obtainMessage(SOCKET_ERROR_RETRY, -1, -1, mDevice);
                     mSessionHandler.sendMessageDelayed(msg, 1500);
                 } else {
-                    markConnectionFailed(btSocket);
+                    markConnectionFailed(mBtSocket);
                 }
             }
         }
 
         @Override
         public void run() {
-            timestamp = System.currentTimeMillis();
-            if (D) Log.d(TAG, "sdp initiated = " + mSdpInitiated + " l2cChannel :" + l2cChannel);
+            mTimestamp = System.currentTimeMillis();
+            if (D) {
+                Log.d(TAG, "sdp initiated = " + mSdpInitiated + " l2cChannel :" + mL2cChannel);
+            }
             // check if sdp initiated successfully for l2cap or not. If not
             // connect
             // directly to rfcomm
-            if (!mSdpInitiated || l2cChannel < 0) {
+            if (!mSdpInitiated || mL2cChannel < 0) {
                 /* sdp failed for some reason, connect on rfcomm */
                 Log.d(TAG, "sdp not initiated, connecting on rfcomm");
                 connectRfcommSocket();
@@ -725,12 +785,12 @@
 
             /* Use BluetoothSocket to connect */
             try {
-                if (isInterrupted) {
+                if (mIsInterrupted) {
                     Log.e(TAG, "btSocket connect interrupted ");
-                    markConnectionFailed(btSocket);
+                    markConnectionFailed(mBtSocket);
                     return;
                 } else {
-                    btSocket = device.createInsecureL2capSocket(l2cChannel);
+                    mBtSocket = mDevice.createInsecureL2capSocket(mL2cChannel);
                 }
             } catch (IOException e1) {
                 Log.e(TAG, "L2cap socket create error", e1);
@@ -738,19 +798,22 @@
                 return;
             }
             try {
-                btSocket.connect();
-                if (V)
-                    Log.v(TAG, "L2cap socket connection attempt took "
-                                    + (System.currentTimeMillis() - timestamp) + " ms");
+                mBtSocket.connect();
+                if (V) {
+                    Log.v(TAG, "L2cap socket connection attempt took " + (System.currentTimeMillis()
+                            - mTimestamp) + " ms");
+                }
                 BluetoothObexTransport transport;
-                transport = new BluetoothObexTransport(btSocket);
-                BluetoothOppPreference.getInstance(mContext).setName(device, device.getName());
-                if (V) Log.v(TAG, "Send transport message " + transport.toString());
+                transport = new BluetoothObexTransport(mBtSocket);
+                BluetoothOppPreference.getInstance(mContext).setName(mDevice, mDevice.getName());
+                if (V) {
+                    Log.v(TAG, "Send transport message " + transport.toString());
+                }
                 mSessionHandler.obtainMessage(TRANSPORT_CONNECTED, transport).sendToTarget();
             } catch (IOException e) {
                 Log.e(TAG, "L2cap socket connect exception", e);
                 try {
-                    btSocket.close();
+                    mBtSocket.close();
                 } catch (IOException e3) {
                     Log.e(TAG, "Bluetooth socket close error ", e3);
                 }
@@ -758,25 +821,29 @@
                 return;
             }
         }
+    }
 
-        private void markConnectionFailed(BluetoothSocket s) {
-            if (V) Log.v(TAG, "markConnectionFailed " + s);
-            try {
-                if (s != null) {
-                    s.close();
-                }
-            } catch (IOException e) {
-                if (V) Log.e(TAG, "Error when close socket");
-            }
-            mSessionHandler.obtainMessage(TRANSPORT_ERROR).sendToTarget();
-            return;
+    private void markConnectionFailed(BluetoothSocket s) {
+        if (V) {
+            Log.v(TAG, "markConnectionFailed " + s);
         }
-    };
+        try {
+            if (s != null) {
+                s.close();
+            }
+        } catch (IOException e) {
+            if (V) {
+                Log.e(TAG, "Error when close socket");
+            }
+        }
+        mSessionHandler.obtainMessage(TRANSPORT_ERROR).sendToTarget();
+        return;
+    }
 
     /* update a trivial field of a share to notify Provider the batch status change */
     private void tickShareStatus(BluetoothOppShareInfo share) {
         if (share == null) {
-            Log.d(TAG,"Share is null");
+            Log.d(TAG, "Share is null");
             return;
         }
         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + share.mId);
@@ -792,9 +859,11 @@
      * multiple receive in the same session, we should handle it to fill it into
      * mSession
      */
+
     /**
      * Process when a share is added to current transfer
      */
+    @Override
     public void onShareAdded(int id) {
         BluetoothOppShareInfo info = mBatch.getPendingShare();
         if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {
@@ -802,13 +871,15 @@
             /*
              * TODO what if it's not auto confirmed?
              */
-            if (mCurrentShare != null &&
-                    (mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED ||
-                     mCurrentShare.mConfirm ==
-                     BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED)) {
+            if (mCurrentShare != null && (
+                    mCurrentShare.mConfirm == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED
+                            || mCurrentShare.mConfirm
+                            == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED)) {
                 /* have additional auto confirmed share to process */
-                if (V) Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId +
-                        " from batch " + mBatch.mId);
+                if (V) {
+                    Log.v(TAG, "Transfer continue session for info " + mCurrentShare.mId
+                            + " from batch " + mBatch.mId);
+                }
                 processCurrentShare();
                 confirmStatusChanged();
             }
@@ -822,9 +893,11 @@
      * the share is currently in transfer, cancel it For inbounds transfer,
      * delete share means the current receiving file should be canceled.
      */
+
     /**
      * Process when a share is deleted from current transfer
      */
+    @Override
     public void onShareDeleted(int id) {
 
     }
@@ -832,8 +905,11 @@
     /**
      * Process when current transfer is canceled
      */
+    @Override
     public void onBatchCanceled() {
-        if (V) Log.v(TAG, "Transfer on Batch canceled");
+        if (V) {
+            Log.v(TAG, "Transfer on Batch canceled");
+        }
 
         this.stop();
         mBatch.mStatus = Constants.BATCH_STATUS_FINISHED;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
index 01312e4..74fd872 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
@@ -32,26 +32,25 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
+import android.app.NotificationManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.text.format.Formatter;
 import android.util.Log;
 import android.view.View;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.Toast;
-import android.database.ContentObserver;
-import android.widget.ProgressBar;
 
+import com.android.bluetooth.R;
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
-import android.app.NotificationManager;
-import android.text.format.Formatter;
 
 /**
  * Handle all transfer related dialogs: -Ongoing transfer -Receiving one file
@@ -63,8 +62,8 @@
  * DIALOG_SEND_ONGOING will transition to DIALOG_SEND_COMPLETE_SUCCESS or
  * DIALOG_SEND_COMPLETE_FAIL
  */
-public class BluetoothOppTransferActivity extends AlertActivity implements
-        DialogInterface.OnClickListener {
+public class BluetoothOppTransferActivity extends AlertActivity
+        implements DialogInterface.OnClickListener {
     private static final String TAG = "BluetoothOppTransferActivity";
     private static final boolean D = Constants.DEBUG;
     private static final boolean V = Constants.VERBOSE;
@@ -117,13 +116,15 @@
     private boolean mNeedUpdateButton = false;
 
     private class BluetoothTransferContentObserver extends ContentObserver {
-        public BluetoothTransferContentObserver() {
+        BluetoothTransferContentObserver() {
             super(new Handler());
         }
 
         @Override
         public void onChange(boolean selfChange) {
-            if (V) Log.v(TAG, "received db changes.");
+            if (V) {
+                Log.v(TAG, "received db changes.");
+            }
             mNeedUpdateButton = true;
             updateProgressbar();
         }
@@ -138,7 +139,9 @@
         mTransInfo = new BluetoothOppTransferInfo();
         mTransInfo = BluetoothOppUtility.queryRecord(this, mUri);
         if (mTransInfo == null) {
-            if (V) Log.e(TAG, "Error: Can not get data from db");
+            if (V) {
+                Log.e(TAG, "Error: Can not get data from db");
+            }
             finish();
             return;
         }
@@ -167,7 +170,9 @@
 
     @Override
     protected void onDestroy() {
-        if (D) Log.d(TAG, "onDestroy()");
+        if (D) {
+            Log.d(TAG, "onDestroy()");
+        }
 
         if (mObserver != null) {
             getContentResolver().unregisterContentObserver(mObserver);
@@ -181,31 +186,33 @@
         boolean isComplete = BluetoothShare.isStatusCompleted(mTransInfo.mStatus);
 
         if (direction == BluetoothShare.DIRECTION_INBOUND) {
-            if (isComplete == true) {
-                if (isSuccess == true) {
+            if (isComplete) {
+                if (isSuccess) {
                     // should not go here
                     mWhichDialog = DIALOG_RECEIVE_COMPLETE_SUCCESS;
-                } else if (isSuccess == false) {
+                } else if (!isSuccess) {
                     mWhichDialog = DIALOG_RECEIVE_COMPLETE_FAIL;
                 }
-            } else if (isComplete == false) {
+            } else if (!isComplete) {
                 mWhichDialog = DIALOG_RECEIVE_ONGOING;
             }
         } else if (direction == BluetoothShare.DIRECTION_OUTBOUND) {
-            if (isComplete == true) {
-                if (isSuccess == true) {
+            if (isComplete) {
+                if (isSuccess) {
                     mWhichDialog = DIALOG_SEND_COMPLETE_SUCCESS;
 
-                } else if (isSuccess == false) {
+                } else if (!isSuccess) {
                     mWhichDialog = DIALOG_SEND_COMPLETE_FAIL;
                 }
-            } else if (isComplete == false) {
+            } else if (!isComplete) {
                 mWhichDialog = DIALOG_SEND_ONGOING;
             }
         }
 
-        if (V) Log.v(TAG, " WhichDialog/dir/isComplete/failOrSuccess" + mWhichDialog + direction
+        if (V) {
+            Log.v(TAG, " WhichDialog/dir/isComplete/failOrSuccess" + mWhichDialog + direction
                     + isComplete + isSuccess);
+        }
     }
 
     private void setUpDialog() {
@@ -243,8 +250,8 @@
 
         mView = getLayoutInflater().inflate(R.layout.file_transfer, null);
 
-        mProgressTransfer = (ProgressBar)mView.findViewById(R.id.progress_transfer);
-        mPercentView = (TextView)mView.findViewById(R.id.progress_percent);
+        mProgressTransfer = (ProgressBar) mView.findViewById(R.id.progress_transfer);
+        mPercentView = (TextView) mView.findViewById(R.id.progress_percent);
 
         customizeViewContent();
 
@@ -263,17 +270,17 @@
 
         if (mWhichDialog == DIALOG_RECEIVE_ONGOING
                 || mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) {
-            mLine1View = (TextView)mView.findViewById(R.id.line1_view);
+            mLine1View = (TextView) mView.findViewById(R.id.line1_view);
             tmp = getString(R.string.download_line1, mTransInfo.mDeviceName);
             mLine1View.setText(tmp);
-            mLine2View = (TextView)mView.findViewById(R.id.line2_view);
+            mLine2View = (TextView) mView.findViewById(R.id.line2_view);
             tmp = getString(R.string.download_line2, mTransInfo.mFileName);
             mLine2View.setText(tmp);
-            mLine3View = (TextView)mView.findViewById(R.id.line3_view);
-            tmp = getString(R.string.download_line3, Formatter.formatFileSize(this,
-                    mTransInfo.mTotalBytes));
+            mLine3View = (TextView) mView.findViewById(R.id.line3_view);
+            tmp = getString(R.string.download_line3,
+                    Formatter.formatFileSize(this, mTransInfo.mTotalBytes));
             mLine3View.setText(tmp);
-            mLine5View = (TextView)mView.findViewById(R.id.line5_view);
+            mLine5View = (TextView) mView.findViewById(R.id.line5_view);
             if (mWhichDialog == DIALOG_RECEIVE_ONGOING) {
                 tmp = getString(R.string.download_line5);
             } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) {
@@ -282,17 +289,17 @@
             mLine5View.setText(tmp);
         } else if (mWhichDialog == DIALOG_SEND_ONGOING
                 || mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) {
-            mLine1View = (TextView)mView.findViewById(R.id.line1_view);
+            mLine1View = (TextView) mView.findViewById(R.id.line1_view);
             tmp = getString(R.string.upload_line1, mTransInfo.mDeviceName);
             mLine1View.setText(tmp);
-            mLine2View = (TextView)mView.findViewById(R.id.line2_view);
+            mLine2View = (TextView) mView.findViewById(R.id.line2_view);
             tmp = getString(R.string.download_line2, mTransInfo.mFileName);
             mLine2View.setText(tmp);
-            mLine3View = (TextView)mView.findViewById(R.id.line3_view);
-            tmp = getString(R.string.upload_line3, mTransInfo.mFileType, Formatter.formatFileSize(
-                    this, mTransInfo.mTotalBytes));
+            mLine3View = (TextView) mView.findViewById(R.id.line3_view);
+            tmp = getString(R.string.upload_line3, mTransInfo.mFileType,
+                    Formatter.formatFileSize(this, mTransInfo.mTotalBytes));
             mLine3View.setText(tmp);
-            mLine5View = (TextView)mView.findViewById(R.id.line5_view);
+            mLine5View = (TextView) mView.findViewById(R.id.line5_view);
             if (mWhichDialog == DIALOG_SEND_ONGOING) {
                 tmp = getString(R.string.upload_line5);
             } else if (mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) {
@@ -301,42 +308,44 @@
             mLine5View.setText(tmp);
         } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_FAIL) {
             if (mTransInfo.mStatus == BluetoothShare.STATUS_ERROR_SDCARD_FULL) {
-                mLine1View = (TextView)mView.findViewById(R.id.line1_view);
+                mLine1View = (TextView) mView.findViewById(R.id.line1_view);
                 tmp = getString(R.string.bt_sm_2_1, mTransInfo.mDeviceName);
                 mLine1View.setText(tmp);
-                mLine2View = (TextView)mView.findViewById(R.id.line2_view);
+                mLine2View = (TextView) mView.findViewById(R.id.line2_view);
                 tmp = getString(R.string.download_fail_line2, mTransInfo.mFileName);
                 mLine2View.setText(tmp);
-                mLine3View = (TextView)mView.findViewById(R.id.line3_view);
-                tmp = getString(R.string.bt_sm_2_2, Formatter.formatFileSize(this,
-                        mTransInfo.mTotalBytes));
+                mLine3View = (TextView) mView.findViewById(R.id.line3_view);
+                tmp = getString(R.string.bt_sm_2_2,
+                        Formatter.formatFileSize(this, mTransInfo.mTotalBytes));
                 mLine3View.setText(tmp);
             } else {
-                mLine1View = (TextView)mView.findViewById(R.id.line1_view);
+                mLine1View = (TextView) mView.findViewById(R.id.line1_view);
                 tmp = getString(R.string.download_fail_line1);
                 mLine1View.setText(tmp);
-                mLine2View = (TextView)mView.findViewById(R.id.line2_view);
+                mLine2View = (TextView) mView.findViewById(R.id.line2_view);
                 tmp = getString(R.string.download_fail_line2, mTransInfo.mFileName);
                 mLine2View.setText(tmp);
-                mLine3View = (TextView)mView.findViewById(R.id.line3_view);
-                tmp = getString(R.string.download_fail_line3, BluetoothOppUtility
-                        .getStatusDescription(this, mTransInfo.mStatus, mTransInfo.mDeviceName));
+                mLine3View = (TextView) mView.findViewById(R.id.line3_view);
+                tmp = getString(R.string.download_fail_line3,
+                        BluetoothOppUtility.getStatusDescription(this, mTransInfo.mStatus,
+                                mTransInfo.mDeviceName));
                 mLine3View.setText(tmp);
             }
-            mLine5View = (TextView)mView.findViewById(R.id.line5_view);
+            mLine5View = (TextView) mView.findViewById(R.id.line5_view);
             mLine5View.setVisibility(View.GONE);
         } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) {
-            mLine1View = (TextView)mView.findViewById(R.id.line1_view);
+            mLine1View = (TextView) mView.findViewById(R.id.line1_view);
             tmp = getString(R.string.upload_fail_line1, mTransInfo.mDeviceName);
             mLine1View.setText(tmp);
-            mLine2View = (TextView)mView.findViewById(R.id.line2_view);
+            mLine2View = (TextView) mView.findViewById(R.id.line2_view);
             tmp = getString(R.string.upload_fail_line1_2, mTransInfo.mFileName);
             mLine2View.setText(tmp);
-            mLine3View = (TextView)mView.findViewById(R.id.line3_view);
-            tmp = getString(R.string.download_fail_line3, BluetoothOppUtility.getStatusDescription(
-                    this, mTransInfo.mStatus, mTransInfo.mDeviceName));
+            mLine3View = (TextView) mView.findViewById(R.id.line3_view);
+            tmp = getString(R.string.download_fail_line3,
+                    BluetoothOppUtility.getStatusDescription(this, mTransInfo.mStatus,
+                            mTransInfo.mDeviceName));
             mLine3View.setText(tmp);
-            mLine5View = (TextView)mView.findViewById(R.id.line5_view);
+            mLine5View = (TextView) mView.findViewById(R.id.line5_view);
             mLine5View.setVisibility(View.GONE);
         }
 
@@ -346,6 +355,7 @@
         }
     }
 
+    @Override
     public void onClick(DialogInterface dialog, int which) {
         switch (which) {
             case DialogInterface.BUTTON_POSITIVE:
@@ -358,8 +368,8 @@
                     BluetoothOppUtility.updateVisibilityToHidden(this, mUri);
 
                     // clear correspondent notification item
-                    ((NotificationManager)getSystemService(NOTIFICATION_SERVICE))
-                            .cancel(mTransInfo.mID);
+                    ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(
+                            mTransInfo.mID);
                 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) {
                     // "try again"
 
@@ -367,25 +377,30 @@
                     BluetoothOppUtility.updateVisibilityToHidden(this, mUri);
 
                     // clear correspondent notification item
-                    ((NotificationManager)getSystemService(NOTIFICATION_SERVICE))
-                            .cancel(mTransInfo.mID);
+                    ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(
+                            mTransInfo.mID);
 
                     // retry the failed transfer
+                    Uri uri = BluetoothOppUtility.originalUri(Uri.parse(mTransInfo.mFileUri));
+                    BluetoothOppSendFileInfo sendFileInfo =
+                            BluetoothOppSendFileInfo.generateFileInfo(BluetoothOppTransferActivity
+                            .this, uri, mTransInfo.mFileType, false);
+                    uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
+                    BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
+                    mTransInfo.mFileUri = uri.toString();
                     BluetoothOppUtility.retryTransfer(this, mTransInfo);
 
                     BluetoothDevice remoteDevice = mAdapter.getRemoteDevice(mTransInfo.mDestAddr);
 
                     // Display toast message
-                    Toast.makeText(
-                            this,
-                            this.getString(R.string.bt_toast_4, BluetoothOppManager.getInstance(
-                                    this).getDeviceName(remoteDevice)), Toast.LENGTH_SHORT)
-                            .show();
+                    Toast.makeText(this, this.getString(R.string.bt_toast_4,
+                            BluetoothOppManager.getInstance(this).getDeviceName(remoteDevice)),
+                            Toast.LENGTH_SHORT).show();
 
                 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) {
                     BluetoothOppUtility.updateVisibilityToHidden(this, mUri);
-                    ((NotificationManager)getSystemService(NOTIFICATION_SERVICE))
-                            .cancel(mTransInfo.mID);
+                    ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(
+                            mTransInfo.mID);
                 }
                 break;
 
@@ -402,8 +417,8 @@
                     }
                     Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
 
-                    ((NotificationManager)getSystemService(NOTIFICATION_SERVICE))
-                            .cancel(mTransInfo.mID);
+                    ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(
+                            mTransInfo.mID);
                 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) {
 
                     BluetoothOppUtility.updateVisibilityToHidden(this, mUri);
@@ -419,7 +434,9 @@
     private void updateProgressbar() {
         mTransInfo = BluetoothOppUtility.queryRecord(this, mUri);
         if (mTransInfo == null) {
-            if (V) Log.e(TAG, "Error: Can not get data from db");
+            if (V) {
+                Log.e(TAG, "Error: Can not get data from db");
+            }
             return;
         }
 
@@ -427,11 +444,13 @@
         mProgressTransfer.setMax(100);
 
         if (mTransInfo.mTotalBytes != 0) {
-            if (V) Log.v(TAG, "mCurrentBytes: " + mTransInfo.mCurrentBytes +
-                " mTotalBytes: " + mTransInfo.mTotalBytes + " (" +
-                (int)((mTransInfo.mCurrentBytes * 100) / mTransInfo.mTotalBytes) + "%)");
-            mProgressTransfer.setProgress((int)((mTransInfo.mCurrentBytes * 100) /
-                mTransInfo.mTotalBytes));
+            if (V) {
+                Log.v(TAG, "mCurrentBytes: " + mTransInfo.mCurrentBytes + " mTotalBytes: "
+                        + mTransInfo.mTotalBytes + " (" + (int) ((mTransInfo.mCurrentBytes * 100)
+                        / mTransInfo.mTotalBytes) + "%)");
+            }
+            mProgressTransfer.setProgress(
+                    (int) ((mTransInfo.mCurrentBytes * 100) / mTransInfo.mTotalBytes));
         } else {
             mProgressTransfer.setProgress(100);
         }
@@ -445,6 +464,10 @@
         // DIALOG_SEND_COMPLETE_SUCCESS/DIALOG_SEND_COMPLETE_FAIL
         if (!mIsComplete && BluetoothShare.isStatusCompleted(mTransInfo.mStatus)
                 && mNeedUpdateButton) {
+            if (mObserver != null) {
+                getContentResolver().unregisterContentObserver(mObserver);
+                mObserver = null;
+            }
             displayWhichDialog();
             updateButton();
             customizeViewContent();
@@ -457,23 +480,23 @@
     private void updateButton() {
         if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_SUCCESS) {
             mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-            mAlert.getButton(DialogInterface.BUTTON_POSITIVE).setText(
-                    getString(R.string.download_succ_ok));
+            mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
+                    .setText(getString(R.string.download_succ_ok));
         } else if (mWhichDialog == DIALOG_RECEIVE_COMPLETE_FAIL) {
             mAlert.setIcon(mAlert.getIconAttributeResId(android.R.attr.alertDialogIcon));
             mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-            mAlert.getButton(DialogInterface.BUTTON_POSITIVE).setText(
-                    getString(R.string.download_fail_ok));
+            mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
+                    .setText(getString(R.string.download_fail_ok));
         } else if (mWhichDialog == DIALOG_SEND_COMPLETE_SUCCESS) {
             mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-            mAlert.getButton(DialogInterface.BUTTON_POSITIVE).setText(
-                    getString(R.string.upload_succ_ok));
+            mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
+                    .setText(getString(R.string.upload_succ_ok));
         } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) {
             mAlert.setIcon(mAlert.getIconAttributeResId(android.R.attr.alertDialogIcon));
-            mAlert.getButton(DialogInterface.BUTTON_POSITIVE).setText(
-                    getString(R.string.upload_fail_ok));
-            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setText(
-                    getString(R.string.upload_fail_cancel));
+            mAlert.getButton(DialogInterface.BUTTON_POSITIVE)
+                    .setText(getString(R.string.upload_fail_ok));
+            mAlert.getButton(DialogInterface.BUTTON_NEGATIVE)
+                    .setText(getString(R.string.upload_fail_cancel));
         }
     }
 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java b/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
index 690a646..7221c53 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferAdapter.java
@@ -32,21 +32,21 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.text.format.DateUtils;
 import android.text.format.DateFormat;
+import android.text.format.DateUtils;
 import android.text.format.Formatter;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.ResourceCursorAdapter;
 import android.widget.TextView;
 
+import com.android.bluetooth.R;
+
 import java.util.Date;
 
 /**
@@ -67,7 +67,7 @@
         Resources r = context.getResources();
 
         // Retrieve the icon for this transfer
-        ImageView iv = (ImageView)view.findViewById(R.id.transfer_icon);
+        ImageView iv = (ImageView) view.findViewById(R.id.transfer_icon);
         int status = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS));
         int dir = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION));
         if (BluetoothShare.isStatusError(status)) {
@@ -81,38 +81,37 @@
         }
 
         // Set title
-        TextView tv = (TextView)view.findViewById(R.id.transfer_title);
-        String title = cursor.getString(
-                cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
+        TextView tv = (TextView) view.findViewById(R.id.transfer_title);
+        String title = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
         if (title == null) {
             title = mContext.getString(R.string.unknown_file);
         }
         tv.setText(title);
 
         // target device
-        tv = (TextView)view.findViewById(R.id.targetdevice);
+        tv = (TextView) view.findViewById(R.id.targetdevice);
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         int destinationColumnId = cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION);
-        BluetoothDevice remoteDevice = adapter.getRemoteDevice(cursor
-                .getString(destinationColumnId));
+        BluetoothDevice remoteDevice =
+                adapter.getRemoteDevice(cursor.getString(destinationColumnId));
         String deviceName = BluetoothOppManager.getInstance(context).getDeviceName(remoteDevice);
         tv.setText(deviceName);
 
         // complete text and complete date
         long totalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
         if (BluetoothShare.isStatusCompleted(status)) {
-            tv = (TextView)view.findViewById(R.id.complete_text);
+            tv = (TextView) view.findViewById(R.id.complete_text);
             tv.setVisibility(View.VISIBLE);
             if (BluetoothShare.isStatusError(status)) {
                 tv.setText(BluetoothOppUtility.getStatusDescription(mContext, status, deviceName));
             } else {
                 String completeText;
                 if (dir == BluetoothShare.DIRECTION_INBOUND) {
-                    completeText = r.getString(R.string.download_success, Formatter.formatFileSize(
-                            mContext, totalBytes));
+                    completeText = r.getString(R.string.download_success,
+                            Formatter.formatFileSize(mContext, totalBytes));
                 } else {
-                    completeText = r.getString(R.string.upload_success, Formatter.formatFileSize(
-                            mContext, totalBytes));
+                    completeText = r.getString(R.string.upload_success,
+                            Formatter.formatFileSize(mContext, totalBytes));
                 }
                 tv.setText(completeText);
             }
@@ -120,9 +119,10 @@
             int dateColumnId = cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP);
             long time = cursor.getLong(dateColumnId);
             Date d = new Date(time);
-            CharSequence str = DateUtils.isToday(time) ? DateFormat.getTimeFormat(mContext).format(
-                    d) : DateFormat.getDateFormat(mContext).format(d);
-            tv = (TextView)view.findViewById(R.id.complete_date);
+            CharSequence str =
+                    DateUtils.isToday(time) ? DateFormat.getTimeFormat(mContext).format(d)
+                            : DateFormat.getDateFormat(mContext).format(d);
+            tv = (TextView) view.findViewById(R.id.complete_date);
             tv.setVisibility(View.VISIBLE);
             tv.setText(str);
         }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java b/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
index fd47318..e4f978d 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferHistory.java
@@ -32,15 +32,13 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.bluetooth.BluetoothAdapter;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.ContentResolver;
 import android.database.Cursor;
+import android.database.StaleDataException;
 import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
@@ -54,13 +52,15 @@
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ListView;
 
+import com.android.bluetooth.R;
+
 /**
  * View showing the user's finished bluetooth opp transfers that the user does
  * not confirm. Including outbound and inbound transfers, both successful and
  * failed. *
  */
-public class BluetoothOppTransferHistory extends Activity implements
-        View.OnCreateContextMenuListener, OnItemClickListener {
+public class BluetoothOppTransferHistory extends Activity
+        implements View.OnCreateContextMenuListener, OnItemClickListener {
     private static final String TAG = "BluetoothOppTransferHistory";
 
     private static final boolean V = Constants.VERBOSE;
@@ -86,11 +86,10 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         setContentView(R.layout.bluetooth_transfers_page);
-        mListView = (ListView)findViewById(R.id.list);
+        mListView = (ListView) findViewById(R.id.list);
         mListView.setEmptyView(findViewById(R.id.empty));
 
-        mShowAllIncoming = getIntent().getBooleanExtra(
-                Constants.EXTRA_SHOW_ALL_FILES, false);
+        mShowAllIncoming = getIntent().getBooleanExtra(Constants.EXTRA_SHOW_ALL_FILES, false);
 
         String direction;
         int dir = getIntent().getIntExtra("direction", 0);
@@ -111,28 +110,33 @@
         String selection = BluetoothShare.STATUS + " >= '200' AND " + direction;
 
         if (!mShowAllIncoming) {
-            selection = selection + " AND ("
-                    + BluetoothShare.VISIBILITY + " IS NULL OR "
-                    + BluetoothShare.VISIBILITY + " == '"
-                    + BluetoothShare.VISIBILITY_VISIBLE + "')";
+            selection = selection + " AND (" + BluetoothShare.VISIBILITY + " IS NULL OR "
+                    + BluetoothShare.VISIBILITY + " == '" + BluetoothShare.VISIBILITY_VISIBLE
+                    + "')";
         }
 
         final String sortOrder = BluetoothShare.TIMESTAMP + " DESC";
 
-        mTransferCursor = getContentResolver().query(BluetoothShare.CONTENT_URI,
-                new String[] {"_id", BluetoothShare.FILENAME_HINT, BluetoothShare.STATUS,
-                        BluetoothShare.TOTAL_BYTES, BluetoothShare._DATA, BluetoothShare.TIMESTAMP,
-                        BluetoothShare.VISIBILITY, BluetoothShare.DESTINATION,
-                        BluetoothShare.DIRECTION},
-                selection, null, sortOrder);
+        mTransferCursor = getContentResolver().query(BluetoothShare.CONTENT_URI, new String[]{
+                "_id",
+                BluetoothShare.FILENAME_HINT,
+                BluetoothShare.STATUS,
+                BluetoothShare.TOTAL_BYTES,
+                BluetoothShare._DATA,
+                BluetoothShare.TIMESTAMP,
+                BluetoothShare.VISIBILITY,
+                BluetoothShare.DESTINATION,
+                BluetoothShare.DIRECTION
+        }, selection, null, sortOrder);
 
         // only attach everything to the listbox if we can access
         // the transfer database. Otherwise, just show it empty
         if (mTransferCursor != null) {
             mIdColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare._ID);
             // Create a list "controller" for the data
-            mTransferAdapter = new BluetoothOppTransferAdapter(this,
-                    R.layout.bluetooth_transfer_item, mTransferCursor);
+            mTransferAdapter =
+                    new BluetoothOppTransferAdapter(this, R.layout.bluetooth_transfer_item,
+                            mTransferCursor);
             mListView.setAdapter(mTransferAdapter);
             mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
             mListView.setOnCreateContextMenuListener(this);
@@ -155,8 +159,7 @@
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         if (!mShowAllIncoming) {
-            boolean showClear = getClearableCount() > 0;
-            menu.findItem(R.id.transfer_menu_clear_all).setEnabled(showClear);
+            menu.findItem(R.id.transfer_menu_clear_all).setEnabled(isTransferComplete());
         }
         return super.onPrepareOptionsMenu(menu);
     }
@@ -206,12 +209,12 @@
     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
         if (mTransferCursor != null) {
             mContextMenu = true;
-            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
+            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
             mTransferCursor.moveToPosition(info.position);
             mContextMenuPosition = info.position;
 
-            String fileName = mTransferCursor.getString(mTransferCursor
-                    .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
+            String fileName = mTransferCursor.getString(
+                    mTransferCursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
             if (fileName == null) {
                 fileName = this.getString(R.string.unknown_file);
             }
@@ -230,31 +233,37 @@
      * Prompt the user if they would like to clear the transfer history
      */
     private void promptClearList() {
-        new AlertDialog.Builder(this).setTitle(R.string.transfer_clear_dlg_title).setMessage(
-                R.string.transfer_clear_dlg_msg).setPositiveButton(android.R.string.ok,
-                new DialogInterface.OnClickListener() {
+        new AlertDialog.Builder(this).setTitle(R.string.transfer_clear_dlg_title)
+                .setMessage(R.string.transfer_clear_dlg_msg)
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    @Override
                     public void onClick(DialogInterface dialog, int whichButton) {
                         clearAllDownloads();
                     }
-                }).setNegativeButton(android.R.string.cancel, null).show();
+                })
+                .setNegativeButton(android.R.string.cancel, null)
+                .show();
     }
 
     /**
-     * Get the number of finished transfers, including error and success.
+     * Returns true if the device has finished transfers, including error and success.
      */
-    private int getClearableCount() {
-        int count = 0;
-        if (mTransferCursor.moveToFirst()) {
-            while (!mTransferCursor.isAfterLast()) {
-                int statusColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare.STATUS);
-                int status = mTransferCursor.getInt(statusColumnId);
-                if (BluetoothShare.isStatusCompleted(status)) {
-                    count++;
+    private boolean isTransferComplete() {
+        try {
+            if (mTransferCursor.moveToFirst()) {
+                while (!mTransferCursor.isAfterLast()) {
+                    int statusColumnId = mTransferCursor
+                                             .getColumnIndexOrThrow(BluetoothShare.STATUS);
+                    int status = mTransferCursor.getInt(statusColumnId);
+                    if (BluetoothShare.isStatusCompleted(status)) {
+                        return true;
+                    }
+                    mTransferCursor.moveToNext();
                 }
-                mTransferCursor.moveToNext();
             }
+        } catch (StaleDataException e) {
         }
-        return count;
+        return false;
     }
 
     /**
@@ -279,9 +288,12 @@
      * android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget
      * .AdapterView, android.view.View, int, long)
      */
+    @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         // Open the selected item
-        if (V) Log.v(TAG, "onItemClick: ContextMenu = " + mContextMenu);
+        if (V) {
+            Log.v(TAG, "onItemClick: ContextMenu = " + mContextMenu);
+        }
         if (!mContextMenu) {
             mTransferCursor.moveToPosition(position);
             openCompleteTransfer();
@@ -323,7 +335,9 @@
     private void updateNotificationWhenBtDisabled() {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (!adapter.isEnabled()) {
-            if (V) Log.v(TAG, "Bluetooth is not enabled, update notification manually.");
+            if (V) {
+                Log.v(TAG, "Bluetooth is not enabled, update notification manually.");
+            }
             mNotifier.updateNotification();
         }
     }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 11f9324..1b5cd59 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -32,25 +32,29 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-import com.google.android.collect.Lists;
-
+import android.app.NotificationManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.net.Uri;
+import android.content.ActivityNotFoundException;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
+import android.net.Uri;
 import android.os.Environment;
 import android.util.Log;
 
+import com.android.bluetooth.R;
+
+import com.google.android.collect.Lists;
+
 import java.io.File;
 import java.io.IOException;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -63,8 +67,8 @@
     private static final boolean D = Constants.DEBUG;
     private static final boolean V = Constants.VERBOSE;
 
-    private static final ConcurrentHashMap<Uri, BluetoothOppSendFileInfo> sSendFileMap
-            = new ConcurrentHashMap<Uri, BluetoothOppSendFileInfo>();
+    private static final ConcurrentHashMap<Uri, BluetoothOppSendFileInfo> sSendFileMap =
+            new ConcurrentHashMap<Uri, BluetoothOppSendFileInfo>();
 
     public static boolean isBluetoothShareUri(Uri uri) {
         return uri.toString().startsWith(BluetoothShare.CONTENT_URI.toString());
@@ -80,7 +84,9 @@
             cursor.close();
         } else {
             info = null;
-            if (V) Log.v(TAG, "BluetoothOppManager Error: not got data from db for uri:" + uri);
+            if (V) {
+                Log.v(TAG, "BluetoothOppManager Error: not got data from db for uri:" + uri);
+            }
         }
         return info;
     }
@@ -89,22 +95,17 @@
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         info.mID = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
         info.mStatus = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS));
-        info.mDirection = cursor.getInt(cursor
-                .getColumnIndexOrThrow(BluetoothShare.DIRECTION));
-        info.mTotalBytes = cursor.getLong(cursor
-                .getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
-        info.mCurrentBytes = cursor.getLong(cursor
-                .getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
-        info.mTimeStamp = cursor.getLong(cursor
-                .getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
-        info.mDestAddr = cursor.getString(cursor
-                .getColumnIndexOrThrow(BluetoothShare.DESTINATION));
+        info.mDirection = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION));
+        info.mTotalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
+        info.mCurrentBytes =
+                cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
+        info.mTimeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
+        info.mDestAddr = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION));
 
-        info.mFileName = cursor.getString(cursor
-                .getColumnIndexOrThrow(BluetoothShare._DATA));
+        info.mFileName = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA));
         if (info.mFileName == null) {
-            info.mFileName = cursor.getString(cursor
-                    .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
+            info.mFileName =
+                    cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
         }
         if (info.mFileName == null) {
             info.mFileName = context.getString(R.string.unknown_file);
@@ -120,21 +121,21 @@
             info.mFileType = context.getContentResolver().getType(u);
         }
         if (info.mFileType == null) {
-            info.mFileType = cursor.getString(cursor
-                    .getColumnIndexOrThrow(BluetoothShare.MIMETYPE));
+            info.mFileType =
+                    cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.MIMETYPE));
         }
 
         BluetoothDevice remoteDevice = adapter.getRemoteDevice(info.mDestAddr);
-        info.mDeviceName =
-                BluetoothOppManager.getInstance(context).getDeviceName(remoteDevice);
+        info.mDeviceName = BluetoothOppManager.getInstance(context).getDeviceName(remoteDevice);
 
-        int confirmationType = cursor.getInt(
-                cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
+        int confirmationType =
+                cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
         info.mHandoverInitiated =
                 confirmationType == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
 
-        if (V) Log.v(TAG, "Get data from db:" + info.mFileName + info.mFileType
-                    + info.mDestAddr);
+        if (V) {
+            Log.v(TAG, "Get data from db:" + info.mFileName + info.mFileType + info.mDestAddr);
+        }
     }
 
     /**
@@ -143,19 +144,18 @@
     // This function is used when UI show batch transfer. Currently only show single transfer.
     public static ArrayList<String> queryTransfersInBatch(Context context, Long timeStamp) {
         ArrayList<String> uris = Lists.newArrayList();
-        final String WHERE = BluetoothShare.TIMESTAMP + " == " + timeStamp;
-
-        Cursor metadataCursor = context.getContentResolver().query(BluetoothShare.CONTENT_URI,
-                new String[] {
-                    BluetoothShare._DATA
-                }, WHERE, null, BluetoothShare._ID);
+        final String where = BluetoothShare.TIMESTAMP + " == " + timeStamp;
+        Cursor metadataCursor =
+                context.getContentResolver().query(BluetoothShare.CONTENT_URI, new String[]{
+                        BluetoothShare._DATA
+                }, where, null, BluetoothShare._ID);
 
         if (metadataCursor == null) {
             return null;
         }
 
-        for (metadataCursor.moveToFirst(); !metadataCursor.isAfterLast(); metadataCursor
-                .moveToNext()) {
+        for (metadataCursor.moveToFirst(); !metadataCursor.isAfterLast();
+                metadataCursor.moveToNext()) {
             String fileName = metadataCursor.getString(0);
             Uri path = Uri.parse(fileName);
             // If there is no scheme, then it must be a file
@@ -163,7 +163,9 @@
                 path = Uri.fromFile(new File(fileName));
             }
             uris.add(path.toString());
-            if (V) Log.d(TAG, "Uri in this batch: " + path.toString());
+            if (V) {
+                Log.d(TAG, "Uri in this batch: " + path.toString());
+            }
         }
         metadataCursor.close();
         return uris;
@@ -195,13 +197,15 @@
 
             // Due to the file is not existing, delete related info in btopp db
             // to prevent this file from appearing in live folder
-            if (V) Log.d(TAG, "This uri will be deleted: " + uri);
+            if (V) {
+                Log.d(TAG, "This uri will be deleted: " + uri);
+            }
             context.getContentResolver().delete(uri, null, null);
             return;
         }
 
-        Uri path = BluetoothOppFileProvider.getUriForFile(
-                context, "com.android.bluetooth.opp.fileprovider", f);
+        Uri path = BluetoothOppFileProvider.getUriForFile(context,
+                "com.android.bluetooth.opp.fileprovider", f);
         if (path == null) {
             Log.w(TAG, "Cannot get content URI for the shared file");
             return;
@@ -216,17 +220,20 @@
             activityIntent.setDataAndTypeAndNormalize(path, mimetype);
 
             List<ResolveInfo> resInfoList = context.getPackageManager()
-                .queryIntentActivities(activityIntent,
-                        PackageManager.MATCH_DEFAULT_ONLY);
+                    .queryIntentActivities(activityIntent, PackageManager.MATCH_DEFAULT_ONLY);
 
             activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             activityIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
             try {
-                if (V) Log.d(TAG, "ACTION_VIEW intent sent out: " + path + " / " + mimetype);
+                if (V) {
+                    Log.d(TAG, "ACTION_VIEW intent sent out: " + path + " / " + mimetype);
+                }
                 context.startActivity(activityIntent);
             } catch (ActivityNotFoundException ex) {
-                if (V) Log.d(TAG, "no activity for handling ACTION_VIEW intent:  " + mimetype, ex);
+                if (V) {
+                    Log.d(TAG, "no activity for handling ACTION_VIEW intent:  " + mimetype, ex);
+                }
             }
         } else {
             Intent in = new Intent(context, BluetoothOppBtErrorActivity.class);
@@ -244,15 +251,19 @@
     public static boolean isRecognizedFileType(Context context, Uri fileUri, String mimetype) {
         boolean ret = true;
 
-        if (D) Log.d(TAG, "RecognizedFileType() fileUri: " + fileUri + " mimetype: " + mimetype);
+        if (D) {
+            Log.d(TAG, "RecognizedFileType() fileUri: " + fileUri + " mimetype: " + mimetype);
+        }
 
         Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
         mimetypeIntent.setDataAndTypeAndNormalize(fileUri, mimetype);
-        List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(mimetypeIntent,
-                PackageManager.MATCH_DEFAULT_ONLY);
+        List<ResolveInfo> list = context.getPackageManager()
+                .queryIntentActivities(mimetypeIntent, PackageManager.MATCH_DEFAULT_ONLY);
 
         if (list.size() == 0) {
-            if (D) Log.d(TAG, "NO application to handle MIME type " + mimetype);
+            if (D) {
+                Log.d(TAG, "NO application to handle MIME type " + mimetype);
+            }
             ret = false;
         }
         return ret;
@@ -271,14 +282,13 @@
      * Helper function to build the progress text.
      */
     public static String formatProgressText(long totalBytes, long currentBytes) {
-        if (totalBytes <= 0) {
-            return "0%";
+        DecimalFormat df = new DecimalFormat("0%");
+        df.setRoundingMode(RoundingMode.DOWN);
+        double percent = 0.0;
+        if (totalBytes > 0) {
+            percent = currentBytes / (double) totalBytes;
         }
-        long progress = currentBytes * 100 / totalBytes;
-        StringBuilder sb = new StringBuilder();
-        sb.append(progress);
-        sb.append('%');
-        return sb.toString();
+        return df.format(percent);
     }
 
     /**
@@ -306,11 +316,11 @@
             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);
-        } else if ((statusCode == BluetoothShare.STATUS_BAD_REQUEST)
-                || (statusCode == BluetoothShare.STATUS_LENGTH_REQUIRED)
-                || (statusCode == BluetoothShare.STATUS_PRECONDITION_FAILED)
-                || (statusCode == BluetoothShare.STATUS_UNHANDLED_OBEX_CODE)
-                || (statusCode == BluetoothShare.STATUS_OBEX_DATA_ERROR)) {
+        } else if ((statusCode == BluetoothShare.STATUS_BAD_REQUEST) || (statusCode
+                == BluetoothShare.STATUS_LENGTH_REQUIRED) || (statusCode
+                == BluetoothShare.STATUS_PRECONDITION_FAILED) || (statusCode
+                == BluetoothShare.STATUS_UNHANDLED_OBEX_CODE) || (statusCode
+                == BluetoothShare.STATUS_OBEX_DATA_ERROR)) {
             ret = context.getString(R.string.status_protocol_error);
         } else {
             ret = context.getString(R.string.status_unknown_error);
@@ -327,14 +337,38 @@
         values.put(BluetoothShare.MIMETYPE, transInfo.mFileType);
         values.put(BluetoothShare.DESTINATION, transInfo.mDestAddr);
 
-        final Uri contentUri = context.getContentResolver().insert(BluetoothShare.CONTENT_URI,
-                values);
-        if (V) Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: " +
-                transInfo.mDeviceName);
+        final Uri contentUri =
+                context.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
+        if (V) {
+            Log.v(TAG,
+                    "Insert contentUri: " + contentUri + "  to device: " + transInfo.mDeviceName);
+        }
+    }
+
+    static Uri originalUri(Uri uri) {
+        String mUri = uri.toString();
+        int atIndex = mUri.lastIndexOf("@");
+        if (atIndex != -1) {
+            mUri = mUri.substring(0, atIndex);
+            uri = Uri.parse(mUri);
+        }
+        if (V) Log.v(TAG, "originalUri: " + uri);
+        return uri;
+    }
+
+    static Uri generateUri(Uri uri, BluetoothOppSendFileInfo sendFileInfo) {
+        String fileInfo = sendFileInfo.toString();
+        int atIndex = fileInfo.lastIndexOf("@");
+        fileInfo = fileInfo.substring(atIndex);
+        uri = Uri.parse(uri + fileInfo);
+        if (V) Log.v(TAG, "generateUri: " + uri);
+        return uri;
     }
 
     static void putSendFileInfo(Uri uri, BluetoothOppSendFileInfo sendFileInfo) {
-        if (D) Log.d(TAG, "putSendFileInfo: uri=" + uri + " sendFileInfo=" + sendFileInfo);
+        if (D) {
+            Log.d(TAG, "putSendFileInfo: uri=" + uri + " sendFileInfo=" + sendFileInfo);
+        }
         if (sendFileInfo == BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR) {
             Log.e(TAG, "putSendFileInfo: bad sendFileInfo, URI: " + uri);
         }
@@ -342,13 +376,17 @@
     }
 
     static BluetoothOppSendFileInfo getSendFileInfo(Uri uri) {
-        if (D) Log.d(TAG, "getSendFileInfo: uri=" + uri);
+        if (D) {
+            Log.d(TAG, "getSendFileInfo: uri=" + uri);
+        }
         BluetoothOppSendFileInfo info = sSendFileMap.get(uri);
         return (info != null) ? info : BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR;
     }
 
     static void closeSendFileInfo(Uri uri) {
-        if (D) Log.d(TAG, "closeSendFileInfo: uri=" + uri);
+        if (D) {
+            Log.d(TAG, "closeSendFileInfo: uri=" + uri);
+        }
         BluetoothOppSendFileInfo info = sSendFileMap.remove(uri);
         if (info != null && info.mInputStream != null) {
             try {
@@ -393,4 +431,11 @@
             return false;
         }
     }
+
+    protected static void cancelNotification(Context ctx) {
+        NotificationManager nm = (NotificationManager) ctx
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+        nm.cancel(BluetoothOppNotification.NOTIFICATION_ID_PROGRESS);
+    }
+
 }
diff --git a/src/com/android/bluetooth/opp/BluetoothShare.java b/src/com/android/bluetooth/opp/BluetoothShare.java
index 94c1fc2..fe0e547 100644
--- a/src/com/android/bluetooth/opp/BluetoothShare.java
+++ b/src/com/android/bluetooth/opp/BluetoothShare.java
@@ -32,8 +32,8 @@
 
 package com.android.bluetooth.opp;
 
-import android.provider.BaseColumns;
 import android.net.Uri;
+import android.provider.BaseColumns;
 
 /**
  * Exposes constants used to interact with the Bluetooth Share manager's content
@@ -60,13 +60,15 @@
      * transfer complete. The request detail could be retrieved by app * as _ID
      * is specified in the intent's data.
      */
-    public static final String TRANSFER_COMPLETED_ACTION = "android.btopp.intent.action.TRANSFER_COMPLETE";
+    public static final String TRANSFER_COMPLETED_ACTION =
+            "android.btopp.intent.action.TRANSFER_COMPLETE";
 
     /**
      * This is sent by the Bluetooth Share component to indicate there is an
      * incoming file request timeout and need update UI.
      */
-    public static final String USER_CONFIRMATION_TIMEOUT_ACTION = "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT";
+    public static final String USER_CONFIRMATION_TIMEOUT_ACTION =
+            "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT";
 
     /**
      * The name of the column containing the URI of the file being
diff --git a/src/com/android/bluetooth/opp/Constants.java b/src/com/android/bluetooth/opp/Constants.java
index b2c5b36..3bf6cde 100644
--- a/src/com/android/bluetooth/opp/Constants.java
+++ b/src/com/android/bluetooth/opp/Constants.java
@@ -32,240 +32,197 @@
 
 package com.android.bluetooth.opp;
 
-import java.io.IOException;
-import java.util.regex.Pattern;
-
-import javax.obex.HeaderSet;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.util.Log;
 
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import javax.obex.HeaderSet;
+
 /**
- * Bluetooth OPP internal constants definition
+ * Bluetooth OPP internal constant definitions
  */
 public class Constants {
     /** Tag used for debugging/logging */
     public static final String TAG = "BluetoothOpp";
 
     /**
-     * The intent that gets sent when the service must wake up for a retry Note:
-     * only retry Outbound transfer
+     * The intent that gets sent when the service must wake up for a retry
+     * Note: Only retry Outbound transfers
      */
-    public static final String ACTION_RETRY = "android.btopp.intent.action.RETRY";
+    static final String ACTION_RETRY = "android.btopp.intent.action.RETRY";
 
     /** the intent that gets sent when clicking a successful transfer */
-    public static final String ACTION_OPEN = "android.btopp.intent.action.OPEN";
+    static final String ACTION_OPEN = "android.btopp.intent.action.OPEN";
 
     /** the intent that gets sent when clicking outbound transfer notification */
-    public static final String ACTION_OPEN_OUTBOUND_TRANSFER = "android.btopp.intent.action.OPEN_OUTBOUND";
+    static final String ACTION_OPEN_OUTBOUND_TRANSFER = "android.btopp.intent.action.OPEN_OUTBOUND";
 
     /** the intent that gets sent when clicking a inbound transfer notification */
-    public static final String ACTION_OPEN_INBOUND_TRANSFER = "android.btopp.intent.action.OPEN_INBOUND";
+    static final String ACTION_OPEN_INBOUND_TRANSFER = "android.btopp.intent.action.OPEN_INBOUND";
 
     /** the intent that gets sent from the Settings app to show the received files */
-    public static final String ACTION_OPEN_RECEIVED_FILES = "android.btopp.intent.action.OPEN_RECEIVED_FILES";
+    static final String ACTION_OPEN_RECEIVED_FILES =
+            "android.btopp.intent.action.OPEN_RECEIVED_FILES";
 
     /** the intent that whitelists a remote bluetooth device for auto-receive confirmation (NFC) */
-    public static final String ACTION_WHITELIST_DEVICE = "android.btopp.intent.action.WHITELIST_DEVICE";
+    static final String ACTION_WHITELIST_DEVICE = "android.btopp.intent.action.WHITELIST_DEVICE";
 
     /** the intent that can be sent by handover requesters to stop a BTOPP transfer */
-    public static final String ACTION_STOP_HANDOVER = "android.btopp.intent.action.STOP_HANDOVER_TRANSFER";
+    static final String ACTION_STOP_HANDOVER = "android.btopp.intent.action.STOP_HANDOVER_TRANSFER";
 
     /** the intent extra to show all received files in the transfer history */
-    public static final String EXTRA_SHOW_ALL_FILES = "android.btopp.intent.extra.SHOW_ALL";
+    static final String EXTRA_SHOW_ALL_FILES = "android.btopp.intent.extra.SHOW_ALL";
 
     /** the intent that gets sent when clicking an incomplete/failed transfer */
-    public static final String ACTION_LIST = "android.btopp.intent.action.LIST";
+    static final String ACTION_LIST = "android.btopp.intent.action.LIST";
 
     /** the intent that is used for initiating a handover transfer */
-    public static final String ACTION_HANDOVER_SEND =
-            "android.nfc.handover.intent.action.HANDOVER_SEND";
+    static final String ACTION_HANDOVER_SEND = "android.nfc.handover.intent.action.HANDOVER_SEND";
 
     /** the intent that is used for initiating a multi-uri handover transfer */
-    public static final String ACTION_HANDOVER_SEND_MULTIPLE =
+    static final String ACTION_HANDOVER_SEND_MULTIPLE =
             "android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE";
 
     /** the intent that is used for indicating an incoming transfer*/
-    public static final String ACTION_HANDOVER_STARTED =
+    static final String ACTION_HANDOVER_STARTED =
             "android.nfc.handover.intent.action.HANDOVER_STARTED";
 
     /** intent action used to indicate the progress of a handover transfer */
-    public static final String ACTION_BT_OPP_TRANSFER_PROGRESS =
+    static final String ACTION_BT_OPP_TRANSFER_PROGRESS =
             "android.nfc.handover.intent.action.TRANSFER_PROGRESS";
 
     /** intent action used to indicate the completion of a handover transfer */
-    public static final String ACTION_BT_OPP_TRANSFER_DONE =
+    static final String ACTION_BT_OPP_TRANSFER_DONE =
             "android.nfc.handover.intent.action.TRANSFER_DONE";
 
     /** intent extra used to indicate the success of a handover transfer */
-    public static final String EXTRA_BT_OPP_TRANSFER_STATUS =
+    static final String EXTRA_BT_OPP_TRANSFER_STATUS =
             "android.nfc.handover.intent.extra.TRANSFER_STATUS";
 
     /** intent extra used to indicate the address associated with the transfer */
-    public static final String EXTRA_BT_OPP_ADDRESS =
-            "android.nfc.handover.intent.extra.ADDRESS";
+    static final String EXTRA_BT_OPP_ADDRESS = "android.nfc.handover.intent.extra.ADDRESS";
 
-    public static final String EXTRA_BT_OPP_OBJECT_COUNT =
+    static final String EXTRA_BT_OPP_OBJECT_COUNT =
             "android.nfc.handover.intent.extra.OBJECT_COUNT";
 
-    public static final int COUNT_HEADER_UNAVAILABLE = -1;
-    public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
+    static final int COUNT_HEADER_UNAVAILABLE = -1;
+    static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
 
-    public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
+    static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
 
     /** intent extra used to indicate the direction of a handover transfer */
-    public static final String EXTRA_BT_OPP_TRANSFER_DIRECTION =
+    static final String EXTRA_BT_OPP_TRANSFER_DIRECTION =
             "android.nfc.handover.intent.extra.TRANSFER_DIRECTION";
 
-    public static final int DIRECTION_BLUETOOTH_INCOMING = 0;
+    static final int DIRECTION_BLUETOOTH_INCOMING = 0;
 
-    public static final int DIRECTION_BLUETOOTH_OUTGOING = 1;
+    static final int DIRECTION_BLUETOOTH_OUTGOING = 1;
 
     /** intent extra used to provide a unique ID for the transfer */
-    public static final String EXTRA_BT_OPP_TRANSFER_ID =
-            "android.nfc.handover.intent.extra.TRANSFER_ID";
+    static final String EXTRA_BT_OPP_TRANSFER_ID = "android.nfc.handover.intent.extra.TRANSFER_ID";
 
     /** intent extra used to provide progress of the transfer */
-    public static final String EXTRA_BT_OPP_TRANSFER_PROGRESS =
+    static final String EXTRA_BT_OPP_TRANSFER_PROGRESS =
             "android.nfc.handover.intent.extra.TRANSFER_PROGRESS";
 
-    /** intent extra used to provide the Uri where the data was stored
-     * by the handover transfer */
-    public static final String EXTRA_BT_OPP_TRANSFER_URI =
+    /** intent extra used to provide the Uri where the data was stored by the handover transfer */
+    static final String EXTRA_BT_OPP_TRANSFER_URI =
             "android.nfc.handover.intent.extra.TRANSFER_URI";
 
-    /** intent extra used to provide the mime-type of the data in
-     *  the handover transfer */
-    public static final String EXTRA_BT_OPP_TRANSFER_MIMETYPE =
+    /** intent extra used to provide the mime-type of the data in the handover transfer */
+    static final String EXTRA_BT_OPP_TRANSFER_MIMETYPE =
             "android.nfc.handover.intent.extra.TRANSFER_MIME_TYPE";
 
     /** permission needed to be able to receive handover status requests */
-    public static final String HANDOVER_STATUS_PERMISSION =
-            "android.permission.NFC_HANDOVER_STATUS";
+    static final String HANDOVER_STATUS_PERMISSION = "android.permission.NFC_HANDOVER_STATUS";
 
-    /** intent extra that indicates this transfer is a handover from another
-      * transport (NFC, WIFI)
-      */
-    public static final String EXTRA_CONNECTION_HANDOVER =
-            "com.android.intent.extra.CONNECTION_HANDOVER";
+    /** the intent that gets sent when deleting the incoming file confirmation notification */
+    static final String ACTION_HIDE = "android.btopp.intent.action.HIDE";
 
-    /**
-     * the intent that gets sent when deleting the incoming file confirmation notification
-     */
-    public static final String ACTION_HIDE = "android.btopp.intent.action.HIDE";
+    /** the intent that gets sent when accepting the incoming file confirmation notification */
+    static final String ACTION_ACCEPT = "android.btopp.intent.action.ACCEPT";
 
-    /**
-     * the intent that gets sent when accepting the incoming file confirmation notification
-     */
-    public static final String ACTION_ACCEPT = "android.btopp.intent.action.ACCEPT";
-
-    /**
-     * the intent that gets sent when declining the incoming file confirmation notification
-     */
-    public static final String ACTION_DECLINE = "android.btopp.intent.action.DECLINE";
+    /** the intent that gets sent when declining the incoming file confirmation notification */
+    static final String ACTION_DECLINE = "android.btopp.intent.action.DECLINE";
 
     /**
      * the intent that gets sent when deleting the notifications of outbound and
      * inbound completed transfer
      */
-    public static final String ACTION_COMPLETE_HIDE = "android.btopp.intent.action.HIDE_COMPLETE";
+    static final String ACTION_COMPLETE_HIDE = "android.btopp.intent.action.HIDE_COMPLETE";
 
-    /**
-     * the intent that gets sent when clicking a incoming file confirm
-     * notification
-     */
-    public static final String ACTION_INCOMING_FILE_CONFIRM = "android.btopp.intent.action.CONFIRM";
+    /** the intent that gets sent when clicking a incoming file confirm notification */
+    static final String ACTION_INCOMING_FILE_CONFIRM = "android.btopp.intent.action.CONFIRM";
 
-    public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
+    static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
 
-    /**
-     * The column that is used to remember whether the media scanner was invoked
-     */
-    public static final String MEDIA_SCANNED = "scanned";
+    /** The column that is used to remember whether the media scanner was invoked */
+    static final String MEDIA_SCANNED = "scanned";
 
-    public static final int MEDIA_SCANNED_NOT_SCANNED = 0;
+    static final int MEDIA_SCANNED_NOT_SCANNED = 0;
 
-    public static final int MEDIA_SCANNED_SCANNED_OK = 1;
+    static final int MEDIA_SCANNED_SCANNED_OK = 1;
 
-    public static final int MEDIA_SCANNED_SCANNED_FAILED = 2;
+    static final int MEDIA_SCANNED_SCANNED_FAILED = 2;
 
     /**
      * The MIME type(s) of we could accept from other device.
      * This is in essence a "white list" of acceptable types.
      * Today, restricted to images, audio, video and certain text types.
      */
-    public static final String[] ACCEPTABLE_SHARE_INBOUND_TYPES = new String[] {
-        "image/*",
-        "video/*",
-        "audio/*",
-        "text/x-vcard",
-        "text/x-vcalendar",
-        "text/calendar",
-        "text/plain",
-        "text/html",
-        "text/xml",
-        "application/zip",
-        "application/vnd.ms-excel",
-        "application/msword",
-        "application/vnd.ms-powerpoint",
-        "application/pdf",
-        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
-        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
-        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
-        "application/x-hwp",
+    static final String[] ACCEPTABLE_SHARE_INBOUND_TYPES = new String[]{
+            "image/*",
+            "video/*",
+            "audio/*",
+            "text/x-vcard",
+            "text/x-vcalendar",
+            "text/calendar",
+            "text/plain",
+            "text/html",
+            "text/xml",
+            "application/zip",
+            "application/vnd.ms-excel",
+            "application/msword",
+            "application/vnd.ms-powerpoint",
+            "application/pdf",
+            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+            "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+            "application/x-hwp",
     };
 
-    /**
-     * The MIME type(s) of we could not accept from other device. TODO: define
-     * correct type list
-     */
-    public static final String[] UNACCEPTABLE_SHARE_INBOUND_TYPES = new String[] {};
+    /** Where we store received files */
+    static final String DEFAULT_STORE_SUBDIR = "/bluetooth";
 
-    /** Where we store Bluetooth received files on the external storage */
-    public static final String DEFAULT_STORE_SUBDIR = "/bluetooth";
+    /** Notify NFC of the transfer progress periodically, or it will timeout after 20sec. */
+    static final int NFC_ALIVE_CHECK_MS = 10000;
 
-    /**
-     * Debug level logging
-     */
-    public static final boolean DEBUG = true;
+    static final boolean DEBUG = true;
 
-    /**
-     * Verbose level logging
-     */
-    public static final boolean VERBOSE = false;
+    static final boolean VERBOSE = false;
 
-    /** use TCP socket instead of Rfcomm Socket to develop */
-    public static final boolean USE_TCP_DEBUG = false;
+    static final int MAX_RECORDS_IN_DATABASE = 50;
 
-    /** use simple TCP server started from TestActivity */
-    public static final boolean USE_TCP_SIMPLE_SERVER = false;
+    static final int BATCH_STATUS_PENDING = 0;
 
-    /** Test TCP socket port */
-    public static final int TCP_DEBUG_PORT = 6500;
+    static final int BATCH_STATUS_RUNNING = 1;
 
-    /** use emulator to debug */
-    public static final boolean USE_EMULATOR_DEBUG = false;
+    static final int BATCH_STATUS_FINISHED = 2;
 
-    public static final int MAX_RECORDS_IN_DATABASE = 1000;
+    static final int BATCH_STATUS_FAILED = 3;
 
-    public static final int BATCH_STATUS_PENDING = 0;
+    static final String BLUETOOTHOPP_NAME_PREFERENCE = "btopp_names";
 
-    public static final int BATCH_STATUS_RUNNING = 1;
+    static final String BLUETOOTHOPP_CHANNEL_PREFERENCE = "btopp_channels";
 
-    public static final int BATCH_STATUS_FINISHED = 2;
+    static final String FILENAME_SEQUENCE_SEPARATOR = "-";
 
-    public static final int BATCH_STATUS_FAILED = 3;
-
-    public static final String BLUETOOTHOPP_NAME_PREFERENCE = "btopp_names";
-
-    public static final String BLUETOOTHOPP_CHANNEL_PREFERENCE = "btopp_channels";
-
-    public static String filename_SEQUENCE_SEPARATOR = "-";
-
-    public static void updateShareStatus(Context context, int id, int status) {
+    static void updateShareStatus(Context context, int id, int status) {
         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);
         ContentValues updateValues = new ContentValues();
         updateValues.put(BluetoothShare.STATUS, status);
@@ -273,11 +230,8 @@
         Constants.sendIntentIfCompleted(context, contentUri, status);
     }
 
-    /*
-     * This function should be called whenever transfer status change to
-     * completed.
-     */
-    public static void sendIntentIfCompleted(Context context, Uri contentUri, int status) {
+    /** This function should be called whenever the transfer status changes to completed. */
+    static void sendIntentIfCompleted(Context context, Uri contentUri, int status) {
         if (BluetoothShare.isStatusCompleted(status)) {
             Intent intent = new Intent(BluetoothShare.TRANSFER_COMPLETED_ACTION);
             intent.setClassName(THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
@@ -286,7 +240,7 @@
         }
     }
 
-    public static boolean mimeTypeMatches(String mimeType, String[] matchAgainst) {
+    static boolean mimeTypeMatches(String mimeType, String[] matchAgainst) {
         for (String matchType : matchAgainst) {
             if (mimeTypeMatches(mimeType, matchType)) {
                 return true;
@@ -295,16 +249,15 @@
         return false;
     }
 
-    public static boolean mimeTypeMatches(String mimeType, String matchAgainst) {
-        Pattern p = Pattern.compile(matchAgainst.replaceAll("\\*", "\\.\\*"),
-                Pattern.CASE_INSENSITIVE);
+    private static boolean mimeTypeMatches(String mimeType, String matchAgainst) {
+        Pattern p =
+                Pattern.compile(matchAgainst.replaceAll("\\*", "\\.\\*"), Pattern.CASE_INSENSITIVE);
         return p.matcher(mimeType).matches();
     }
 
-    public static void logHeader(HeaderSet hs) {
+    static void logHeader(HeaderSet hs) {
         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
         try {
-
             Log.v(TAG, "COUNT : " + hs.getHeader(HeaderSet.COUNT));
             Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
             Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
diff --git a/src/com/android/bluetooth/opp/TestActivity.java b/src/com/android/bluetooth/opp/TestActivity.java
index 821e176..89bf075 100644
--- a/src/com/android/bluetooth/opp/TestActivity.java
+++ b/src/com/android/bluetooth/opp/TestActivity.java
@@ -32,8 +32,6 @@
 
 package com.android.bluetooth.opp;
 
-import com.android.bluetooth.R;
-
 import android.app.Activity;
 import android.content.ContentValues;
 import android.content.Context;
@@ -44,10 +42,12 @@
 import android.os.Handler;
 import android.os.Message;
 import android.util.Log;
-import android.widget.Button;
-import android.widget.EditText;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.android.bluetooth.R;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -86,7 +86,7 @@
 
     EditText mMediaView;
 
-    TestTcpServer server;
+    TestTcpServer mServer;
 
     /** Called when the activity is first created. */
     @Override
@@ -107,7 +107,7 @@
              */
 
             String type = intent.getType();
-            Uri stream = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
+            Uri stream = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
 
             if (stream != null && type != null) {
                 /*
@@ -115,8 +115,8 @@
                  * Email.ACCEPTABLE_ATTACHMENT_SEND_TYPES)) {
                  * addAttachment(stream);
                  */
-                Log.v(Constants.TAG, " Get share intent with Uri " + stream + " mimetype is "
-                        + type);
+                Log.v(Constants.TAG,
+                        " Get share intent with Uri " + stream + " mimetype is " + type);
                 // Log.v(Constants.TAG, " trying Uri function " +
                 // stream.getAuthority() + " " + Uri.parse(stream));
                 Cursor cursor = c.getContentResolver().query(stream, null, null, null, null);
@@ -141,20 +141,20 @@
 
         setContentView(R.layout.testactivity_main);
 
-        Button mInsertRecord = (Button)findViewById(R.id.insert_record);
-        Button mDeleteRecord = (Button)findViewById(R.id.delete_record);
-        Button mUpdateRecord = (Button)findViewById(R.id.update_record);
+        Button mInsertRecord = (Button) findViewById(R.id.insert_record);
+        Button mDeleteRecord = (Button) findViewById(R.id.delete_record);
+        Button mUpdateRecord = (Button) findViewById(R.id.update_record);
 
-        Button mAckRecord = (Button)findViewById(R.id.ack_record);
+        Button mAckRecord = (Button) findViewById(R.id.ack_record);
 
-        Button mDeleteAllRecord = (Button)findViewById(R.id.deleteAll_record);
-        mUpdateView = (EditText)findViewById(R.id.update_text);
-        mAckView = (EditText)findViewById(R.id.ack_text);
-        mDeleteView = (EditText)findViewById(R.id.delete_text);
-        mInsertView = (EditText)findViewById(R.id.insert_text);
+        Button mDeleteAllRecord = (Button) findViewById(R.id.deleteAll_record);
+        mUpdateView = (EditText) findViewById(R.id.update_text);
+        mAckView = (EditText) findViewById(R.id.ack_text);
+        mDeleteView = (EditText) findViewById(R.id.delete_text);
+        mInsertView = (EditText) findViewById(R.id.insert_text);
 
-        mAddressView = (EditText)findViewById(R.id.address_text);
-        mMediaView = (EditText)findViewById(R.id.media_text);
+        mAddressView = (EditText) findViewById(R.id.address_text);
+        mMediaView = (EditText) findViewById(R.id.media_text);
 
         mInsertRecord.setOnClickListener(insertRecordListener);
         mDeleteRecord.setOnClickListener(deleteRecordListener);
@@ -162,10 +162,10 @@
         mAckRecord.setOnClickListener(ackRecordListener);
         mDeleteAllRecord.setOnClickListener(deleteAllRecordListener);
 
-        Button mStartTcpServer = (Button)findViewById(R.id.start_server);
+        Button mStartTcpServer = (Button) findViewById(R.id.start_server);
         mStartTcpServer.setOnClickListener(startTcpServerListener);
 
-        Button mNotifyTcpServer = (Button)findViewById(R.id.notify_server);
+        Button mNotifyTcpServer = (Button) findViewById(R.id.notify_server);
         mNotifyTcpServer.setOnClickListener(notifyTcpServerListener);
         /* parse insert result Uri */
         /*
@@ -214,6 +214,7 @@
     }
 
     public OnClickListener insertRecordListener = new OnClickListener() {
+        @Override
         public void onClick(View view) {
 
             String address = null;
@@ -275,17 +276,19 @@
     };
 
     public OnClickListener deleteRecordListener = new OnClickListener() {
+        @Override
         public void onClick(View view) {
-            Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/"
-                    + mDeleteView.getText().toString());
+            Uri contentUri =
+                    Uri.parse(BluetoothShare.CONTENT_URI + "/" + mDeleteView.getText().toString());
             getContentResolver().delete(contentUri, null, null);
         }
     };
 
     public OnClickListener updateRecordListener = new OnClickListener() {
+        @Override
         public void onClick(View view) {
-            Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/"
-                    + mUpdateView.getText().toString());
+            Uri contentUri =
+                    Uri.parse(BluetoothShare.CONTENT_URI + "/" + mUpdateView.getText().toString());
             ContentValues updateValues = new ContentValues();
             // mCurrentByte ++;
             // updateValues.put(BluetoothShare.TOTAL_BYTES, "120000");
@@ -299,9 +302,10 @@
     };
 
     public OnClickListener ackRecordListener = new OnClickListener() {
+        @Override
         public void onClick(View view) {
-            Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/"
-                    + mAckView.getText().toString());
+            Uri contentUri =
+                    Uri.parse(BluetoothShare.CONTENT_URI + "/" + mAckView.getText().toString());
             ContentValues updateValues = new ContentValues();
             // mCurrentByte ++;
             // updateValues.put(BluetoothShare.TOTAL_BYTES, "120000");
@@ -314,28 +318,31 @@
     };
 
     public OnClickListener deleteAllRecordListener = new OnClickListener() {
+        @Override
         public void onClick(View view) {
-            Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "");
+            Uri contentUri = Uri.parse(String.valueOf(BluetoothShare.CONTENT_URI));
             getContentResolver().delete(contentUri, null, null);
         }
     };
 
     public OnClickListener startTcpServerListener = new OnClickListener() {
+        @Override
         public void onClick(View view) {
-            server = new TestTcpServer();
-            Thread server_thread = new Thread(server);
-            server_thread.start();
-
+            mServer = new TestTcpServer();
+            Thread serverThread = new Thread(mServer);
+            serverThread.start();
         }
     };
 
     public OnClickListener notifyTcpServerListener = new OnClickListener() {
+        @Override
         public void onClick(View view) {
             final Thread notifyThread = new Thread() {
+                @Override
                 public void run() {
-                    synchronized (server) {
-                        server.a = true;
-                        server.notify();
+                    synchronized (mServer) {
+                        mServer.a = true;
+                        mServer.notify();
                     }
                 }
 
@@ -371,11 +378,11 @@
 
     private int mBtOppRfcommChannel = -1;
 
-    public TestTcpListener() {
+    TestTcpListener() {
         this(DEFAULT_OPP_CHANNEL);
     }
 
-    public TestTcpListener(int channel) {
+    TestTcpListener(int channel) {
         mBtOppRfcommChannel = channel;
     }
 
@@ -385,12 +392,16 @@
             mSocketAcceptThread = new Thread(TAG) {
                 ServerSocket mServerSocket;
 
+                @Override
                 public void run() {
-                    if (D) Log.d(TAG, "RfcommSocket listen thread starting");
+                    if (D) {
+                        Log.d(TAG, "RfcommSocket listen thread starting");
+                    }
                     try {
-                        if (V)
-                            Log.v(TAG, "Create server RfcommSocket on channel"
-                                    + mBtOppRfcommChannel);
+                        if (V) {
+                            Log.v(TAG,
+                                    "Create server RfcommSocket on channel" + mBtOppRfcommChannel);
+                        }
                         mServerSocket = new ServerSocket(6500, 1);
                     } catch (IOException e) {
                         Log.e(TAG, "Error listing on channel" + mBtOppRfcommChannel);
@@ -401,11 +412,15 @@
                             mServerSocket.setSoTimeout(ACCEPT_WAIT_TIMEOUT);
                             Socket clientSocket = mServerSocket.accept();
                             if (clientSocket == null) {
-                                if (V) Log.v(TAG, "incomming connection time out");
+                                if (V) {
+                                    Log.v(TAG, "incomming connection time out");
+                                }
                             } else {
-                                if (D) Log.d(TAG, "RfcommSocket connected!");
-                                Log.d(TAG, "remote addr is "
-                                        + clientSocket.getRemoteSocketAddress());
+                                if (D) {
+                                    Log.d(TAG, "RfcommSocket connected!");
+                                }
+                                Log.d(TAG,
+                                        "remote addr is " + clientSocket.getRemoteSocketAddress());
                                 TestTcpTransport transport = new TestTcpTransport(clientSocket);
                                 Message msg = Message.obtain();
                                 msg.setTarget(mCallback);
@@ -423,7 +438,9 @@
                             Log.e(TAG, "socketAcceptThread thread was interrupted (2), exiting");
                         }
                     }
-                    if (D) Log.d(TAG, "RfcommSocket listen thread finished");
+                    if (D) {
+                        Log.d(TAG, "RfcommSocket listen thread finished");
+                    }
                 }
             };
             mInterrupted = false;
@@ -436,16 +453,22 @@
 
     public synchronized void stop() {
         if (mSocketAcceptThread != null) {
-            if (D) Log.d(TAG, "stopping Connect Thread");
+            if (D) {
+                Log.d(TAG, "stopping Connect Thread");
+            }
             mInterrupted = true;
             try {
                 mSocketAcceptThread.interrupt();
-                if (V) Log.v(TAG, "waiting for thread to terminate");
+                if (V) {
+                    Log.v(TAG, "waiting for thread to terminate");
+                }
                 mSocketAcceptThread.join();
                 mSocketAcceptThread = null;
                 mCallback = null;
             } catch (InterruptedException e) {
-                if (V) Log.v(TAG, "Interrupted waiting for Accept Thread to join");
+                if (V) {
+                    Log.v(TAG, "Interrupted waiting for Accept Thread to join");
+                }
             }
         }
     }
@@ -457,15 +480,16 @@
 
     private static final boolean V = Constants.VERBOSE;
 
-    static final int port = 6500;
+    static final int PORT = 6500;
 
     public boolean a = false;
 
     // TextView serverStatus = null;
+    @Override
     public void run() {
         try {
-            updateStatus("[server:] listen on port " + port);
-            TestTcpSessionNotifier rsn = new TestTcpSessionNotifier(port);
+            updateStatus("[server:] listen on port " + PORT);
+            TestTcpSessionNotifier rsn = new TestTcpSessionNotifier(PORT);
 
             updateStatus("[server:] Now waiting for a client to connect");
             rsn.acceptAndOpen(this);
@@ -475,10 +499,11 @@
         }
     }
 
-    public TestTcpServer() {
+    TestTcpServer() {
         updateStatus("enter construtor of TcpServer");
     }
 
+    @Override
     public int onConnect(HeaderSet request, HeaderSet reply) {
 
         updateStatus("[server:] The client has created an OBEX session");
@@ -489,24 +514,27 @@
                     wait(500);
                 }
             } catch (InterruptedException e) {
-                if (V) Log.v(TAG, "Interrupted waiting for markBatchFailed");
+                if (V) {
+                    Log.v(TAG, "Interrupted waiting for markBatchFailed");
+                }
             }
         }
         updateStatus("[server:] we accpet the seesion");
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
+    @Override
     public int onPut(Operation op) {
         FileOutputStream fos = null;
         try {
             java.io.InputStream is = op.openInputStream();
 
-            updateStatus("Got data bytes " + is.available() + " name "
-                    + op.getReceivedHeader().getHeader(HeaderSet.NAME) + " type " + op.getType());
+            updateStatus("Got data bytes " + is.available() + " name " + op.getReceivedHeader()
+                    .getHeader(HeaderSet.NAME) + " type " + op.getType());
 
-            File f = new File((String)op.getReceivedHeader().getHeader(HeaderSet.NAME));
+            File f = new File((String) op.getReceivedHeader().getHeader(HeaderSet.NAME));
             fos = new FileOutputStream(f);
-            byte b[] = new byte[1000];
+            byte[] b = new byte[1000];
             int len;
 
             while (is.available() > 0 && (len = is.read(b)) > 0) {
@@ -529,26 +557,32 @@
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
+    @Override
     public void onDisconnect(HeaderSet req, HeaderSet resp) {
         updateStatus("[server:] The client has disconnected the OBEX session");
     }
 
+    @Override
     public void updateStatus(String message) {
         Log.v(TAG, "\n" + message);
     }
 
+    @Override
     public void onAuthenticationFailure(byte[] userName) {
     }
 
+    @Override
     public int onSetPath(HeaderSet request, HeaderSet reply, boolean backup, boolean create) {
 
         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
     }
 
+    @Override
     public int onDelete(HeaderSet request, HeaderSet reply) {
         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
     }
 
+    @Override
     public int onGet(Operation op) {
         return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
     }
@@ -558,28 +592,28 @@
 class TestTcpSessionNotifier {
     /* implements SessionNotifier */
 
-    ServerSocket server = null;
+    ServerSocket mServer = null;
 
-    Socket conn = null;
+    Socket mConn = null;
 
     private static final String TAG = "TestTcpSessionNotifier";
 
-    public TestTcpSessionNotifier(int port) throws IOException {
-        server = new ServerSocket(port);
+    TestTcpSessionNotifier(int port) throws IOException {
+        mServer = new ServerSocket(port);
     }
 
     public ServerSession acceptAndOpen(ServerRequestHandler handler, Authenticator auth)
             throws IOException {
         try {
-            conn = server.accept();
+            mConn = mServer.accept();
 
         } catch (Exception ex) {
             Log.v(TAG, "ex");
         }
 
-        TestTcpTransport tt = new TestTcpTransport(conn);
+        TestTcpTransport tt = new TestTcpTransport(mConn);
 
-        return new ServerSession((ObexTransport)tt, handler, auth);
+        return new ServerSession((ObexTransport) tt, handler, auth);
 
     }
 
@@ -593,55 +627,64 @@
 
 class TestTcpTransport implements ObexTransport {
 
-    Socket s = null;
+    Socket mSocket = null;
 
-    public TestTcpTransport(Socket s) {
+    TestTcpTransport(Socket s) {
         super();
-        this.s = s;
+        this.mSocket = s;
     }
 
+    @Override
     public void close() throws IOException {
-        s.close();
+        mSocket.close();
     }
 
+    @Override
     public DataInputStream openDataInputStream() throws IOException {
         return new DataInputStream(openInputStream());
     }
 
+    @Override
     public DataOutputStream openDataOutputStream() throws IOException {
         return new DataOutputStream(openOutputStream());
     }
 
+    @Override
     public InputStream openInputStream() throws IOException {
-        return s.getInputStream();
+        return mSocket.getInputStream();
     }
 
+    @Override
     public OutputStream openOutputStream() throws IOException {
-        return s.getOutputStream();
+        return mSocket.getOutputStream();
     }
 
+    @Override
     public void connect() throws IOException {
         // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void create() throws IOException {
         // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void disconnect() throws IOException {
         // TODO Auto-generated method stub
 
     }
 
+    @Override
     public void listen() throws IOException {
         // TODO Auto-generated method stub
 
     }
 
     public boolean isConnected() throws IOException {
-        return s.isConnected();
+        return mSocket.isConnected();
     }
 
     @Override
diff --git a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
index 51058e1..f477dd9 100644
--- a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
+++ b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
@@ -19,22 +19,18 @@
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.DhcpResults;
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.ip.IpManager;
-import android.net.ip.IpManager.WaitForProvisioningCallback;
+import android.net.ip.IpClient;
+import android.net.ip.IpClient.WaitForProvisioningCallback;
 import android.os.Looper;
 import android.text.TextUtils;
 import android.util.Slog;
 
-import com.android.bluetooth.pan.PanService;
-import com.android.internal.util.AsyncChannel;
-
 /**
  * This class tracks the data connection associated with Bluetooth
  * reverse tethering. PanService calls it when a reverse tethered
@@ -53,7 +49,7 @@
 
     // All accesses to these must be synchronized(this).
     private final NetworkInfo mNetworkInfo;
-    private IpManager mIpManager;
+    private IpClient mIpClient;
     private String mInterfaceName;
     private NetworkAgent mNetworkAgent;
 
@@ -69,10 +65,10 @@
         setCapabilityFilter(mNetworkCapabilities);
     }
 
-    private void stopIpManagerLocked() {
-        if (mIpManager != null) {
-            mIpManager.shutdown();
-            mIpManager = null;
+    private void stopIpClientLocked() {
+        if (mIpClient != null) {
+            mIpClient.shutdown();
+            mIpClient = null;
         }
     }
 
@@ -82,12 +78,13 @@
     @Override
     protected void startNetwork() {
         // TODO: Figure out how to replace this thread with simple invocations
-        // of IpManager. This will likely necessitate a rethink about
+        // of IpClient. This will likely necessitate a rethink about
         // NetworkAgent, NetworkInfo, and associated instance lifetimes.
         Thread ipProvisioningThread = new Thread(new Runnable() {
+            @Override
             public void run() {
                 LinkProperties linkProperties;
-                final WaitForProvisioningCallback ipmCallback = new WaitForProvisioningCallback() {
+                final WaitForProvisioningCallback ipcCallback = new WaitForProvisioningCallback() {
                     @Override
                     public void onLinkPropertiesChange(LinkProperties newLp) {
                         synchronized (BluetoothTetheringNetworkFactory.this) {
@@ -103,37 +100,41 @@
                         Slog.e(TAG, "attempted to reverse tether without interface name");
                         return;
                     }
-                    log("ipProvisioningThread(+" + mInterfaceName + "): " +
-                            "mNetworkInfo=" + mNetworkInfo);
-                    mIpManager = new IpManager(mContext, mInterfaceName, ipmCallback);
-                    mIpManager.startProvisioning(
-                            mIpManager.buildProvisioningConfiguration()
-                                    .withoutIpReachabilityMonitor()
-                                    .build());
+                    log("ipProvisioningThread(+" + mInterfaceName + "): " + "mNetworkInfo="
+                            + mNetworkInfo);
+                    mIpClient = new IpClient(mContext, mInterfaceName, ipcCallback);
+                    mIpClient.startProvisioning(mIpClient.buildProvisioningConfiguration()
+                            .withoutMultinetworkPolicyTracker()
+                            .withoutIpReachabilityMonitor()
+                            .build());
                     mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, null);
                 }
 
-                linkProperties = ipmCallback.waitForProvisioning();
+                linkProperties = ipcCallback.waitForProvisioning();
                 if (linkProperties == null) {
                     Slog.e(TAG, "IP provisioning error.");
-                    synchronized(BluetoothTetheringNetworkFactory.this) {
-                        stopIpManagerLocked();
+                    synchronized (BluetoothTetheringNetworkFactory.this) {
+                        stopIpClientLocked();
                         setScoreFilter(-1);
                     }
                     return;
                 }
 
-                synchronized(BluetoothTetheringNetworkFactory.this) {
+                synchronized (BluetoothTetheringNetworkFactory.this) {
                     mNetworkInfo.setIsAvailable(true);
                     mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
 
                     // Create our NetworkAgent.
-                    mNetworkAgent = new NetworkAgent(getLooper(), mContext, NETWORK_TYPE,
-                            mNetworkInfo, mNetworkCapabilities, linkProperties, NETWORK_SCORE) {
-                        public void unwanted() {
-                            BluetoothTetheringNetworkFactory.this.onCancelRequest();
-                        };
-                    };
+                    mNetworkAgent =
+                            new NetworkAgent(getLooper(), mContext, NETWORK_TYPE, mNetworkInfo,
+                                    mNetworkCapabilities, linkProperties, NETWORK_SCORE) {
+                                @Override
+                                public void unwanted() {
+                                    BluetoothTetheringNetworkFactory.this.onCancelRequest();
+                                }
+
+                                ;
+                            };
                 }
             }
         });
@@ -149,7 +150,7 @@
 
     // Called by the NetworkFactory, NetworkAgent or PanService to tear down network.
     private synchronized void onCancelRequest() {
-        stopIpManagerLocked();
+        stopIpClientLocked();
         mInterfaceName = "";
 
         mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
@@ -158,7 +159,7 @@
             mNetworkAgent = null;
         }
         for (BluetoothDevice device : mPanService.getConnectedDevices()) {
-             mPanService.disconnect(device);
+            mPanService.disconnect(device);
         }
     }
 
@@ -169,7 +170,7 @@
             Slog.e(TAG, "attempted to reverse tether with empty interface");
             return;
         }
-        synchronized(this) {
+        synchronized (this) {
             if (!TextUtils.isEmpty(mInterfaceName)) {
                 Slog.e(TAG, "attempted to reverse tether while already in process");
                 return;
@@ -197,6 +198,7 @@
         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
         // Bluetooth v3 and v4 go up to 24 Mbps.
         // TODO: Adjust this to actual connection bandwidth.
         mNetworkCapabilities.setLinkUpstreamBandwidthKbps(24 * 1000);
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
old mode 100755
new mode 100644
index a978693..700b395
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -27,9 +27,9 @@
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Binder;
 import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.ServiceManager;
@@ -37,8 +37,10 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.MetricsLogger;
+import com.android.bluetooth.btservice.ProfileService;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
@@ -55,9 +57,9 @@
     private static final boolean DBG = false;
     private static PanService sPanService;
 
-    private static final String BLUETOOTH_IFACE_ADDR_START= "192.168.44.1";
+    private static final String BLUETOOTH_IFACE_ADDR_START = "192.168.44.1";
     private static final int BLUETOOTH_MAX_PAN_CONNECTIONS = 5;
-    private static final int BLUETOOTH_PREFIX_LENGTH        = 24;
+    private static final int BLUETOOTH_PREFIX_LENGTH = 24;
 
     private HashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices;
     private ArrayList<String> mBluetoothIfaceAddresses;
@@ -78,94 +80,87 @@
         classInitNative();
     }
 
-    protected String getName() {
-        return TAG;
-    }
-
+    @Override
     public IProfileServiceBinder initBinder() {
         return new BluetoothPanBinder(this);
     }
 
     public static synchronized PanService getPanService() {
-        if (sPanService != null && sPanService.isAvailable()) {
-            if (DBG) {
-                Log.d(TAG, "getPanService(): returning " + sPanService);
-            }
-            return sPanService;
+        if (sPanService == null) {
+            Log.w(TAG, "getPanService(): service is null");
+            return null;
         }
-        if (DBG) {
-            if (sPanService == null) {
-                Log.d(TAG, "getPanService(): service is NULL");
-            } else if (!sPanService.isAvailable()) {
-                Log.d(TAG, "getPanService(): service is not available");
-            }
+        if (!sPanService.isAvailable()) {
+            Log.w(TAG, "getPanService(): service is not available ");
+            return null;
         }
-        return null;
+        return sPanService;
     }
 
     private static synchronized void setPanService(PanService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) {
-                Log.d(TAG, "setPanService(): set to: " + instance);
-            }
-            sPanService = instance;
-        } else {
-            if (DBG) {
-                if (instance == null) {
-                    Log.d(TAG, "setPanService(): service not available");
-                } else if (!instance.isAvailable()) {
-                    Log.d(TAG, "setPanService(): service is cleaning up");
-                }
-            }
+        if (DBG) {
+            Log.d(TAG, "setPanService(): set to: " + instance);
         }
+        sPanService = instance;
     }
 
+    @Override
     protected boolean start() {
         mPanDevices = new HashMap<BluetoothDevice, BluetoothPanDevice>();
         mBluetoothIfaceAddresses = new ArrayList<String>();
         try {
             mMaxPanDevices = getResources().getInteger(
-                                 com.android.internal.R.integer.config_max_pan_devices);
+                    com.android.internal.R.integer.config_max_pan_devices);
         } catch (NotFoundException e) {
             mMaxPanDevices = BLUETOOTH_MAX_PAN_CONNECTIONS;
         }
         initializeNative();
-        mNativeAvailable=true;
+        mNativeAvailable = true;
 
-        mNetworkFactory = new BluetoothTetheringNetworkFactory(getBaseContext(), getMainLooper(),
-                this);
+        mNetworkFactory =
+                new BluetoothTetheringNetworkFactory(getBaseContext(), getMainLooper(), this);
         setPanService(this);
 
         return true;
     }
 
+    @Override
     protected boolean stop() {
         mHandler.removeCallbacksAndMessages(null);
         return true;
     }
 
-    protected boolean cleanup() {
+    @Override
+    protected void cleanup() {
+        // TODO(b/72948646): this should be moved to stop()
+        setPanService(null);
         if (mNativeAvailable) {
             cleanupNative();
-            mNativeAvailable=false;
+            mNativeAvailable = false;
         }
-        if(mPanDevices != null) {
-            List<BluetoothDevice> DevList = getConnectedDevices();
-            for(BluetoothDevice dev : DevList) {
-                handlePanDeviceStateChange(dev, mPanIfName, BluetoothProfile.STATE_DISCONNECTED,
-                        BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
+        if (mPanDevices != null) {
+           int[] desiredStates = {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED,
+                                  BluetoothProfile.STATE_DISCONNECTING};
+           List<BluetoothDevice> devList =
+                   getDevicesMatchingConnectionStates(desiredStates);
+           for (BluetoothDevice device : devList) {
+                BluetoothPanDevice panDevice = mPanDevices.get(device);
+                Log.d(TAG, "panDevice: " + panDevice + " device address: " + device);
+                if (panDevice != null) {
+                    handlePanDeviceStateChange(device, mPanIfName,
+                        BluetoothProfile.STATE_DISCONNECTED,
+                        panDevice.mLocalRole, panDevice.mRemoteRole);
+                }
             }
             mPanDevices.clear();
         }
-        return true;
     }
 
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MESSAGE_CONNECT:
-                {
+                case MESSAGE_CONNECT: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
                     if (!connectPanNative(Utils.getByteAddress(device),
                             BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE)) {
@@ -177,11 +172,10 @@
                         break;
                     }
                 }
-                    break;
-                case MESSAGE_DISCONNECT:
-                {
+                break;
+                case MESSAGE_DISCONNECT: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    if (!disconnectPanNative(Utils.getByteAddress(device)) ) {
+                    if (!disconnectPanNative(Utils.getByteAddress(device))) {
                         handlePanDeviceStateChange(device, mPanIfName,
                                 BluetoothProfile.STATE_DISCONNECTING, BluetoothPan.LOCAL_PANU_ROLE,
                                 BluetoothPan.REMOTE_NAP_ROLE);
@@ -191,17 +185,17 @@
                         break;
                     }
                 }
-                    break;
-                case MESSAGE_CONNECT_STATE_CHANGED:
-                {
-                    ConnectState cs = (ConnectState)msg.obj;
+                break;
+                case MESSAGE_CONNECT_STATE_CHANGED: {
+                    ConnectState cs = (ConnectState) msg.obj;
                     BluetoothDevice device = getDevice(cs.addr);
                     // TBD get iface from the msg
                     if (DBG) {
-                        log("MESSAGE_CONNECT_STATE_CHANGED: " + device + " state: " + cs.state);
+                        Log.d(TAG,
+                                "MESSAGE_CONNECT_STATE_CHANGED: " + device + " state: " + cs.state);
                     }
                     handlePanDeviceStateChange(device, mPanIfName /* iface */,
-                            convertHalState(cs.state), cs.local_role,  cs.remote_role);
+                            convertHalState(cs.state), cs.local_role, cs.remote_role);
                 }
                 break;
             }
@@ -214,75 +208,114 @@
     private static class BluetoothPanBinder extends IBluetoothPan.Stub
             implements IProfileServiceBinder {
         private PanService mService;
-        public BluetoothPanBinder(PanService svc) {
+
+        BluetoothPanBinder(PanService svc) {
             mService = svc;
         }
-        public boolean cleanup() {
+
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
+
         private PanService getService() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"Pan call not allowed for non-active user");
+                Log.w(TAG, "Pan call not allowed for non-active user");
                 return null;
             }
 
-            if (mService  != null && mService.isAvailable()) {
+            if (mService != null && mService.isAvailable()) {
                 return mService;
             }
             return null;
         }
+
+        @Override
         public boolean connect(BluetoothDevice device) {
             PanService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.connect(device);
         }
+
+        @Override
         public boolean disconnect(BluetoothDevice device) {
             PanService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.disconnect(device);
         }
+
+        @Override
         public int getConnectionState(BluetoothDevice device) {
             PanService service = getService();
-            if (service == null) return BluetoothPan.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothPan.STATE_DISCONNECTED;
+            }
             return service.getConnectionState(device);
         }
+
         private boolean isPanNapOn() {
             PanService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isPanNapOn();
         }
+
         private boolean isPanUOn() {
-            if(DBG) Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
+            if (DBG) {
+                Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
+            }
             PanService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isPanUOn();
         }
+
+        @Override
         public boolean isTetheringOn() {
             // TODO(BT) have a variable marking the on/off state
             PanService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.isTetheringOn();
         }
+
+        @Override
         public void setBluetoothTethering(boolean value) {
             PanService service = getService();
-            if (service == null) return;
-            Log.d(TAG, "setBluetoothTethering: " + value +", mTetherOn: " + service.mTetherOn);
+            if (service == null) {
+                return;
+            }
+            Log.d(TAG, "setBluetoothTethering: " + value + ", mTetherOn: " + service.mTetherOn);
             service.setBluetoothTethering(value);
         }
 
+        @Override
         public List<BluetoothDevice> getConnectedDevices() {
             PanService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getConnectedDevices();
         }
 
+        @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
             PanService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getDevicesMatchingConnectionStates(states);
         }
-    };
+    }
+
+    ;
 
     public boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
@@ -290,14 +323,14 @@
             Log.e(TAG, "Pan Device not disconnected: " + device);
             return false;
         }
-        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT,device);
+        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT, device);
         mHandler.sendMessage(msg);
         return true;
     }
 
     public boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT,device);
+        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT, device);
         mHandler.sendMessage(msg);
         return true;
     }
@@ -311,20 +344,28 @@
     }
 
     boolean isPanNapOn() {
-        if(DBG) Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
+        if (DBG) {
+            Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
+        }
         return (getPanLocalRoleNative() & BluetoothPan.LOCAL_NAP_ROLE) != 0;
     }
+
     boolean isPanUOn() {
-        if(DBG) Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
+        if (DBG) {
+            Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
+        }
         return (getPanLocalRoleNative() & BluetoothPan.LOCAL_PANU_ROLE) != 0;
     }
+
     public boolean isTetheringOn() {
         // TODO(BT) have a variable marking the on/off state
         return mTetherOn;
     }
 
     void setBluetoothTethering(boolean value) {
-        if(DBG) Log.d(TAG, "setBluetoothTethering: " + value +", mTetherOn: " + mTetherOn);
+        if (DBG) {
+            Log.d(TAG, "setBluetoothTethering: " + value + ", mTetherOn: " + mTetherOn);
+        }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         final Context context = getBaseContext();
         String pkgName = context.getOpPackageName();
@@ -339,15 +380,16 @@
         }
 
         UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
-        if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
+        if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING) && value) {
             throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user.");
         }
-        if(mTetherOn != value) {
+        if (mTetherOn != value) {
             //drop any existing panu or pan-nap connection when changing the tethering state
             mTetherOn = value;
-            List<BluetoothDevice> DevList = getConnectedDevices();
-            for(BluetoothDevice dev : DevList)
+            List<BluetoothDevice> devList = getConnectedDevices();
+            for (BluetoothDevice dev : devList) {
                 disconnect(dev);
+            }
         }
     }
 
@@ -357,16 +399,17 @@
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothPanPriorityKey(device.getAddress()),
-                priority);
+                Settings.Global.getBluetoothPanPriorityKey(device.getAddress()), priority);
         if (DBG) {
-            Log.d(TAG,"Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
-        if (device == null) throw new IllegalArgumentException("Null device");
+        if (device == null) {
+            throw new IllegalArgumentException("Null device");
+        }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         return Settings.Global.getInt(getContentResolver(),
                 Settings.Global.getBluetoothPanPriorityKey(device.getAddress()),
@@ -375,16 +418,16 @@
 
     public List<BluetoothDevice> getConnectedDevices() {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        List<BluetoothDevice> devices = getDevicesMatchingConnectionStates(
-                new int[] {BluetoothProfile.STATE_CONNECTED});
+        List<BluetoothDevice> devices =
+                getDevicesMatchingConnectionStates(new int[]{BluetoothProfile.STATE_CONNECTED});
         return devices;
     }
 
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> panDevices = new ArrayList<BluetoothDevice>();
 
-        for (BluetoothDevice device: mPanDevices.keySet()) {
+        for (BluetoothDevice device : mPanDevices.keySet()) {
             int panDeviceState = getConnectionState(device);
             for (int state : states) {
                 if (state == panDeviceState) {
@@ -396,35 +439,43 @@
         return panDevices;
     }
 
-    static protected class ConnectState {
-        public ConnectState(byte[] address, int state, int error, int local_role, int remote_role) {
+    protected static class ConnectState {
+        public ConnectState(byte[] address, int state, int error, int localRole, int remoteRole) {
             this.addr = address;
             this.state = state;
             this.error = error;
-            this.local_role = local_role;
-            this.remote_role = remote_role;
+            this.local_role = localRole;
+            this.remote_role = remoteRole;
         }
-        byte[] addr;
-        int state;
-        int error;
-        int local_role;
-        int remote_role;
-    };
-    private void onConnectStateChanged(byte[] address, int state, int error, int local_role,
-            int remote_role) {
+
+        public byte[] addr;
+        public int state;
+        public int error;
+        public int local_role;
+        public int remote_role;
+    }
+
+    ;
+
+    private void onConnectStateChanged(byte[] address, int state, int error, int localRole,
+            int remoteRole) {
         if (DBG) {
-            log("onConnectStateChanged: " + state + ", local role:" + local_role +
-                    ", remote_role: " + remote_role);
+            Log.d(TAG, "onConnectStateChanged: " + state + ", local role:" + localRole
+                    + ", remoteRole: " + remoteRole);
         }
         Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
-        msg.obj = new ConnectState(address, state, error, local_role, remote_role);
+        msg.obj = new ConnectState(address, state, error, localRole, remoteRole);
         mHandler.sendMessage(msg);
     }
-    private void onControlStateChanged(int local_role, int state, int error, String ifname) {
-        if (DBG)
-            log("onControlStateChanged: " + state + ", error: " + error + ", ifname: " + ifname);
-        if(error == 0)
+
+    private void onControlStateChanged(int localRole, int state, int error, String ifname) {
+        if (DBG) {
+            Log.d(TAG, "onControlStateChanged: " + state + ", error: " + error + ", ifname: "
+                    + ifname);
+        }
+        if (error == 0) {
             mPanIfName = ifname;
+        }
     }
 
     private static int convertHalState(int halState) {
@@ -443,12 +494,12 @@
         }
     }
 
-    void handlePanDeviceStateChange(BluetoothDevice device,
-                                    String iface, int state, int local_role, int remote_role) {
-        if(DBG) {
-            Log.d(TAG, "handlePanDeviceStateChange: device: " + device + ", iface: " + iface +
-                    ", state: " + state + ", local_role:" + local_role + ", remote_role:" +
-                    remote_role);
+    void handlePanDeviceStateChange(BluetoothDevice device, String iface, int state, int localRole,
+            int remoteRole) {
+        if (DBG) {
+            Log.d(TAG, "handlePanDeviceStateChange: device: " + device + ", iface: " + iface
+                    + ", state: " + state + ", localRole:" + localRole + ", remoteRole:"
+                    + remoteRole);
         }
         int prevState;
 
@@ -456,12 +507,13 @@
         if (panDevice == null) {
             Log.i(TAG, "state " + state + " Num of connected pan devices: " + mPanDevices.size());
             prevState = BluetoothProfile.STATE_DISCONNECTED;
-            panDevice = new BluetoothPanDevice(state, iface, local_role);
+            panDevice = new BluetoothPanDevice(state, iface, localRole, remoteRole);
             mPanDevices.put(device, panDevice);
         } else {
             prevState = panDevice.mState;
             panDevice.mState = state;
-            panDevice.mLocalRole = local_role;
+            panDevice.mLocalRole = localRole;
+            panDevice.mRemoteRole = remoteRole;
             panDevice.mIface = iface;
         }
 
@@ -470,17 +522,20 @@
         // connect call will put us in STATE_DISCONNECTED. Then, the disconnect completes and
         // changes the state to STATE_DISCONNECTING. All future calls to BluetoothPan#connect
         // will fail until the caller explicitly calls BluetoothPan#disconnect.
-        if (prevState == BluetoothProfile.STATE_DISCONNECTED && state == BluetoothProfile.STATE_DISCONNECTING) {
+        if (prevState == BluetoothProfile.STATE_DISCONNECTED
+                && state == BluetoothProfile.STATE_DISCONNECTING) {
             Log.d(TAG, "Ignoring state change from " + prevState + " to " + state);
             mPanDevices.remove(device);
             return;
         }
 
         Log.d(TAG, "handlePanDeviceStateChange preState: " + prevState + " state: " + state);
-        if (prevState == state) return;
-        if (remote_role == BluetoothPan.LOCAL_PANU_ROLE) {
+        if (prevState == state) {
+            return;
+        }
+        if (remoteRole == BluetoothPan.LOCAL_PANU_ROLE) {
             if (state == BluetoothProfile.STATE_CONNECTED) {
-                if ((!mTetherOn) || (local_role == BluetoothPan.LOCAL_PANU_ROLE)) {
+                if ((!mTetherOn) || (localRole == BluetoothPan.LOCAL_PANU_ROLE)) {
                     Log.d(TAG, "handlePanDeviceStateChange BT tethering is off/Local role"
                             + " is PANU drop the connection");
                     mPanDevices.remove(device);
@@ -508,28 +563,28 @@
             }
         } else if (mNetworkFactory != null) {
             // PANU Role = reverse Tether
-            Log.d(TAG, "handlePanDeviceStateChange LOCAL_PANU_ROLE:REMOTE_NAP_ROLE state = " +
-                    state + ", prevState = " + prevState);
+            Log.d(TAG, "handlePanDeviceStateChange LOCAL_PANU_ROLE:REMOTE_NAP_ROLE state = " + state
+                    + ", prevState = " + prevState);
             if (state == BluetoothProfile.STATE_CONNECTED) {
                 mNetworkFactory.startReverseTether(iface);
-           } else if (state == BluetoothProfile.STATE_DISCONNECTED &&
-                   (prevState == BluetoothProfile.STATE_CONNECTED ||
-                   prevState == BluetoothProfile.STATE_DISCONNECTING)) {
+            } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
                 mNetworkFactory.stopReverseTether();
                 mPanDevices.remove(device);
             }
         }
 
+        if (state == BluetoothProfile.STATE_CONNECTED) {
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.PAN);
+        }
         /* Notifying the connection state change of the profile before sending the intent for
            connection state change, as it was causing a race condition, with the UI not being
            updated with the correct connection state. */
-        Log.d(TAG, "Pan Device state : device: " + device + " State:" +
-                       prevState + "->" + state);
+        Log.d(TAG, "Pan Device state : device: " + device + " State:" + prevState + "->" + state);
         Intent intent = new Intent(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothPan.EXTRA_PREVIOUS_STATE, prevState);
         intent.putExtra(BluetoothPan.EXTRA_STATE, state);
-        intent.putExtra(BluetoothPan.EXTRA_LOCAL_ROLE, local_role);
+        intent.putExtra(BluetoothPan.EXTRA_LOCAL_ROLE, localRole);
         sendBroadcast(intent, BLUETOOTH_PERM);
     }
 
@@ -547,7 +602,7 @@
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
         ConnectivityManager cm =
-            (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
         String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
 
         // bring toggle the interfaces
@@ -560,14 +615,16 @@
         }
 
         boolean found = false;
-        for (String currIface: currentIfaces) {
+        for (String currIface : currentIfaces) {
             if (currIface.equals(iface)) {
                 found = true;
                 break;
             }
         }
 
-        if (!found) return null;
+        if (!found) {
+            return null;
+        }
 
         InterfaceConfiguration ifcg = null;
         String address = null;
@@ -576,9 +633,9 @@
             if (ifcg != null) {
                 InetAddress addr = null;
                 LinkAddress linkAddr = ifcg.getLinkAddress();
-                if (linkAddr == null || (addr = linkAddr.getAddress()) == null ||
-                        addr.equals(NetworkUtils.numericToInetAddress("0.0.0.0")) ||
-                        addr.equals(NetworkUtils.numericToInetAddress("::0"))) {
+                if (linkAddr == null || (addr = linkAddr.getAddress()) == null || addr.equals(
+                        NetworkUtils.numericToInetAddress("0.0.0.0")) || addr.equals(
+                        NetworkUtils.numericToInetAddress("::0"))) {
                     address = BLUETOOTH_IFACE_ADDR_START;
                     addr = NetworkUtils.numericToInetAddress(address);
                 }
@@ -596,12 +653,12 @@
                 if (enable) {
                     int tetherStatus = cm.tether(iface);
                     if (tetherStatus != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
-                        Log.e(TAG, "Error tethering "+ iface + " tetherStatus: " + tetherStatus);
+                        Log.e(TAG, "Error tethering " + iface + " tetherStatus: " + tetherStatus);
                         return null;
                     }
                 } else {
                     int untetherStatus = cm.untether(iface);
-                    Log.i(TAG, "Untethered: "+ iface + " untetherStatus: " + untetherStatus);
+                    Log.i(TAG, "Untethered: " + iface + " untetherStatus: " + untetherStatus);
                 }
             }
         } catch (Exception e) {
@@ -614,7 +671,7 @@
     private List<BluetoothDevice> getConnectedPanDevices() {
         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
 
-        for (BluetoothDevice device: mPanDevices.keySet()) {
+        for (BluetoothDevice device : mPanDevices.keySet()) {
             if (getPanDeviceConnectionState(device) == BluetoothProfile.STATE_CONNECTED) {
                 devices.add(device);
             }
@@ -646,27 +703,35 @@
         private int mState;
         private String mIface;
         private int mLocalRole; // Which local role is this PAN device bound to
+        private int mRemoteRole; // Which remote role is this PAN device bound to
 
-        BluetoothPanDevice(int state, String iface, int localRole) {
+        BluetoothPanDevice(int state, String iface, int localRole, int remoteRole) {
             mState = state;
             mIface = iface;
             mLocalRole = localRole;
+            mRemoteRole = remoteRole;
         }
     }
 
     // Constants matching Hal header file bt_hh.h
     // bthh_connection_state_t
-    private final static int CONN_STATE_CONNECTED = 0;
-    private final static int CONN_STATE_CONNECTING = 1;
-    private final static int CONN_STATE_DISCONNECTED = 2;
-    private final static int CONN_STATE_DISCONNECTING = 3;
+    private static final int CONN_STATE_CONNECTED = 0;
+    private static final int CONN_STATE_CONNECTING = 1;
+    private static final int CONN_STATE_DISCONNECTED = 2;
+    private static final int CONN_STATE_DISCONNECTING = 3;
 
-    private native static void classInitNative();
+    private static native void classInitNative();
+
     private native void initializeNative();
+
     private native void cleanupNative();
-    private native boolean connectPanNative(byte[] btAddress, int local_role, int remote_role);
+
+    private native boolean connectPanNative(byte[] btAddress, int localRole, int remoteRole);
+
     private native boolean disconnectPanNative(byte[] btAddress);
-    private native boolean enablePanNative(int local_role);
+
+    private native boolean enablePanNative(int localRole);
+
     private native int getPanLocalRoleNative();
 
 }
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
index b841aaf..f709cd5 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
@@ -32,8 +32,7 @@
 
 package com.android.bluetooth.pbap;
 
-import com.android.bluetooth.R;
-
+import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -44,15 +43,15 @@
 import android.os.Message;
 import android.preference.Preference;
 import android.text.InputFilter;
-import android.text.TextWatcher;
 import android.text.InputFilter.LengthFilter;
+import android.text.TextWatcher;
 import android.util.Log;
 import android.view.View;
-import android.widget.CheckBox;
+import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;
-import android.widget.Button;
 
+import com.android.bluetooth.R;
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 
@@ -61,8 +60,9 @@
  * the other prompts the user to enter a session key for authentication with a
  * remote Bluetooth device.
  */
-public class BluetoothPbapActivity extends AlertActivity implements
-        DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener, TextWatcher {
+public class BluetoothPbapActivity extends AlertActivity
+        implements DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener,
+        TextWatcher {
     private static final String TAG = "BluetoothPbapActivity";
 
     private static final boolean V = BluetoothPbapService.VERBOSE;
@@ -77,7 +77,7 @@
 
     private EditText mKeyView;
 
-    private TextView messageView;
+    private TextView mMessageView;
 
     private String mSessionKey = "";
 
@@ -85,16 +85,14 @@
 
     private Button mOkButton;
 
-    private CheckBox mAlwaysAllowed;
-
     private boolean mTimeout = false;
 
-    private boolean mAlwaysAllowedValue = true;
-
     private static final int DISMISS_TIMEOUT_DIALOG = 0;
 
     private static final int DISMISS_TIMEOUT_DIALOG_VALUE = 2000;
 
+    private BluetoothDevice mDevice;
+
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -110,6 +108,7 @@
         super.onCreate(savedInstanceState);
         Intent i = getIntent();
         String action = i.getAction();
+        mDevice = i.getParcelableExtra(BluetoothPbapService.EXTRA_DEVICE);
         if (action.equals(BluetoothPbapService.AUTH_CHALL_ACTION)) {
             showPbapDialog(DIALOG_YES_NO_AUTH);
             mCurrentDialog = DIALOG_YES_NO_AUTH;
@@ -118,8 +117,8 @@
                     + "PBAP_ACCESS_REQUEST or PBAP_AUTH_CHALL ");
             finish();
         }
-        registerReceiver(mReceiver, new IntentFilter(
-                BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION));
+        registerReceiver(mReceiver,
+                new IntentFilter(BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION));
     }
 
     private void showPbapDialog(int id) {
@@ -142,10 +141,9 @@
     }
 
     private String createDisplayText(final int id) {
-        String mRemoteName = BluetoothPbapService.getRemoteDeviceName();
         switch (id) {
             case DIALOG_YES_NO_AUTH:
-                String mMessage2 = getString(R.string.pbap_session_key_dialog_title, mRemoteName);
+                String mMessage2 = getString(R.string.pbap_session_key_dialog_title, mDevice);
                 return mMessage2;
             default:
                 return null;
@@ -156,12 +154,12 @@
         switch (id) {
             case DIALOG_YES_NO_AUTH:
                 mView = getLayoutInflater().inflate(R.layout.auth, null);
-                messageView = (TextView)mView.findViewById(R.id.message);
-                messageView.setText(createDisplayText(id));
-                mKeyView = (EditText)mView.findViewById(R.id.text);
+                mMessageView = (TextView) mView.findViewById(R.id.message);
+                mMessageView.setText(createDisplayText(id));
+                mKeyView = (EditText) mView.findViewById(R.id.text);
                 mKeyView.addTextChangedListener(this);
-                mKeyView.setFilters(new InputFilter[] {
-                    new LengthFilter(BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH)
+                mKeyView.setFilters(new InputFilter[]{
+                        new LengthFilter(BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH)
                 });
                 return mView;
             default:
@@ -193,22 +191,14 @@
             final String extraValue) {
         Intent intent = new Intent(intentName);
         intent.setPackage(BluetoothPbapService.THIS_PACKAGE_NAME);
+        intent.putExtra(BluetoothPbapService.EXTRA_DEVICE, mDevice);
         if (extraName != null) {
             intent.putExtra(extraName, extraValue);
         }
         sendBroadcast(intent);
     }
 
-    private void sendIntentToReceiver(final String intentName, final String extraName,
-            final boolean extraValue) {
-        Intent intent = new Intent(intentName);
-        intent.setPackage(BluetoothPbapService.THIS_PACKAGE_NAME);
-        if (extraName != null) {
-            intent.putExtra(extraName, extraValue);
-        }
-        sendBroadcast(intent);
-    }
-
+    @Override
     public void onClick(DialogInterface dialog, int which) {
         switch (which) {
             case DialogInterface.BUTTON_POSITIVE:
@@ -229,8 +219,7 @@
     private void onTimeout() {
         mTimeout = true;
         if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
-            messageView.setText(getString(R.string.pbap_authentication_timeout_message,
-                    BluetoothPbapService.getRemoteDeviceName()));
+            mMessageView.setText(getString(R.string.pbap_authentication_timeout_message, mDevice));
             mKeyView.setVisibility(View.GONE);
             mKeyView.clearFocus();
             mKeyView.removeTextChangedListener(this);
@@ -246,7 +235,9 @@
     protected void onRestoreInstanceState(Bundle savedInstanceState) {
         super.onRestoreInstanceState(savedInstanceState);
         mTimeout = savedInstanceState.getBoolean(KEY_USER_TIMEOUT);
-        if (V) Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout);
+        if (V) {
+            Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout);
+        }
         if (mTimeout) {
             onTimeout();
         }
@@ -264,16 +255,20 @@
         unregisterReceiver(mReceiver);
     }
 
+    @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         return true;
     }
 
+    @Override
     public void beforeTextChanged(CharSequence s, int start, int before, int after) {
     }
 
+    @Override
     public void onTextChanged(CharSequence s, int start, int before, int count) {
     }
 
+    @Override
     public void afterTextChanged(android.text.Editable s) {
         if (s.length() > 0) {
             mOkButton.setEnabled(true);
@@ -285,7 +280,9 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case DISMISS_TIMEOUT_DIALOG:
-                    if (V) Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
+                    if (V) {
+                        Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg.");
+                    }
                     finish();
                     break;
                 default:
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapAuthenticator.java b/src/com/android/bluetooth/pbap/BluetoothPbapAuthenticator.java
index e9cca90..69100d5 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapAuthenticator.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapAuthenticator.java
@@ -32,8 +32,6 @@
 
 package com.android.bluetooth.pbap;
 
-import android.os.Handler;
-import android.os.Message;
 import android.util.Log;
 
 import javax.obex.Authenticator;
@@ -44,61 +42,61 @@
  * authentication procedure.
  */
 public class BluetoothPbapAuthenticator implements Authenticator {
-    private static final String TAG = "BluetoothPbapAuthenticator";
+    private static final String TAG = "PbapAuthenticator";
 
     private boolean mChallenged;
-
     private boolean mAuthCancelled;
-
     private String mSessionKey;
+    private PbapStateMachine mPbapStateMachine;
 
-    private Handler mCallback;
-
-    public BluetoothPbapAuthenticator(final Handler callback) {
-        mCallback = callback;
+    BluetoothPbapAuthenticator(final PbapStateMachine stateMachine) {
+        mPbapStateMachine = stateMachine;
         mChallenged = false;
         mAuthCancelled = false;
         mSessionKey = null;
     }
 
-    public final synchronized void setChallenged(final boolean bool) {
+    final synchronized void setChallenged(final boolean bool) {
         mChallenged = bool;
+        notify();
     }
 
-    public final synchronized void setCancelled(final boolean bool) {
+    final synchronized void setCancelled(final boolean bool) {
         mAuthCancelled = bool;
+        notify();
     }
 
-    public final synchronized void setSessionKey(final String string) {
+    final synchronized void setSessionKey(final String string) {
         mSessionKey = string;
     }
 
     private void waitUserConfirmation() {
-        Message msg = Message.obtain(mCallback);
-        msg.what = BluetoothPbapService.MSG_OBEX_AUTH_CHALL;
-        msg.sendToTarget();
+        mPbapStateMachine.sendMessage(PbapStateMachine.CREATE_NOTIFICATION);
+        mPbapStateMachine.sendMessageDelayed(PbapStateMachine.REMOVE_NOTIFICATION,
+                BluetoothPbapService.USER_CONFIRM_TIMEOUT_VALUE);
         synchronized (this) {
             while (!mChallenged && !mAuthCancelled) {
                 try {
                     wait();
                 } catch (InterruptedException e) {
-                    Log.e(TAG, "Interrupted while waiting on isChalled");
+                    Log.e(TAG, "Interrupted while waiting on isChallenged or AuthCancelled");
                 }
             }
         }
     }
 
+    @Override
     public PasswordAuthentication onAuthenticationChallenge(final String description,
             final boolean isUserIdRequired, final boolean isFullAccess) {
         waitUserConfirmation();
         if (mSessionKey.trim().length() != 0) {
-            PasswordAuthentication pa = new PasswordAuthentication(null, mSessionKey.getBytes());
-            return pa;
+            return new PasswordAuthentication(null, mSessionKey.getBytes());
         }
         return null;
     }
 
     // TODO: Reserved for future use only, in case PSE challenge PCE
+    @Override
     public byte[] onAuthenticationResponse(final byte[] userName) {
         byte[] b = null;
         return b;
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java b/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
index 325a386..805f3ea 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
@@ -15,8 +15,6 @@
  */
 package com.android.bluetooth.pbap;
 
-import com.android.bluetooth.R;
-
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
@@ -28,6 +26,7 @@
 import android.text.format.Time;
 import android.util.Log;
 
+import com.android.bluetooth.R;
 import com.android.vcard.VCardBuilder;
 import com.android.vcard.VCardConfig;
 import com.android.vcard.VCardConstants;
@@ -42,24 +41,28 @@
     private static final String TAG = "CallLogComposer";
 
     private static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
-        "Failed to get database information";
+            "Failed to get database information";
 
-    private static final String FAILURE_REASON_NO_ENTRY =
-        "There's no exportable in the database";
+    private static final String FAILURE_REASON_NO_ENTRY = "There's no exportable in the database";
 
     private static final String FAILURE_REASON_NOT_INITIALIZED =
-        "The vCard composer object is not correctly initialized";
+            "The vCard composer object is not correctly initialized";
 
     /** Should be visible only from developers... (no need to translate, hopefully) */
     private static final String FAILURE_REASON_UNSUPPORTED_URI =
-        "The Uri vCard composer received is not supported by the composer.";
+            "The Uri vCard composer received is not supported by the composer.";
 
     private static final String NO_ERROR = "No error";
 
     /** The projection to use when querying the call log table */
-    private static final String[] sCallLogProjection = new String[] {
-            Calls.NUMBER, Calls.DATE, Calls.TYPE, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
-            Calls.CACHED_NUMBER_LABEL, Calls.NUMBER_PRESENTATION
+    private static final String[] sCallLogProjection = new String[]{
+            Calls.NUMBER,
+            Calls.DATE,
+            Calls.TYPE,
+            Calls.CACHED_NAME,
+            Calls.CACHED_NUMBER_TYPE,
+            Calls.CACHED_NUMBER_LABEL,
+            Calls.NUMBER_PRESENTATION
     };
     private static final int NUMBER_COLUMN_INDEX = 0;
     private static final int DATE_COLUMN_INDEX = 1;
@@ -88,8 +91,8 @@
         mContentResolver = context.getContentResolver();
     }
 
-    public boolean init(final Uri contentUri, final String selection,
-            final String[] selectionArgs, final String sortOrder) {
+    public boolean init(final Uri contentUri, final String selection, final String[] selectionArgs,
+            final String sortOrder) {
         final String[] projection;
         if (CallLog.Calls.CONTENT_URI.equals(contentUri)) {
             projection = sCallLogProjection;
@@ -98,8 +101,8 @@
             return false;
         }
 
-        mCursor = mContentResolver.query(
-                contentUri, projection, selection, selectionArgs, sortOrder);
+        mCursor =
+                mContentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
 
         if (mCursor == null) {
             mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
@@ -134,9 +137,9 @@
     }
 
     private String createOneCallLogEntryInternal(boolean vcardVer21) {
-        final int vcardType = (vcardVer21 ? VCardConfig.VCARD_TYPE_V21_GENERIC :
-                VCardConfig.VCARD_TYPE_V30_GENERIC) |
-                VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING;
+        final int vcardType = (vcardVer21 ? VCardConfig.VCARD_TYPE_V21_GENERIC
+                : VCardConfig.VCARD_TYPE_V30_GENERIC)
+                | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING;
         final VCardBuilder builder = new VCardBuilder(vcardType);
         String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
         String number = mCursor.getString(NUMBER_COLUMN_INDEX);
@@ -169,12 +172,11 @@
     /**
      * This static function is to compose vCard for phone own number
      */
-    public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
-            String phoneNumber, boolean vcardVer21) {
-        final int vcardType = (vcardVer21 ?
-                VCardConfig.VCARD_TYPE_V21_GENERIC :
-                    VCardConfig.VCARD_TYPE_V30_GENERIC) |
-                VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING;
+    public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName, String phoneNumber,
+            boolean vcardVer21) {
+        final int vcardType = (vcardVer21 ? VCardConfig.VCARD_TYPE_V21_GENERIC
+                : VCardConfig.VCARD_TYPE_V30_GENERIC)
+                | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING;
         final VCardBuilder builder = new VCardBuilder(vcardType);
         boolean needCharset = false;
         if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
@@ -195,7 +197,7 @@
      * Format according to RFC 2445 DATETIME type.
      * The format is: ("%Y%m%dT%H%M%S").
      */
-    private final String toRfc2455Format(final long millSecs) {
+    private String toRfc2455Format(final long millSecs) {
         Time startDate = new Time();
         startDate.set(millSecs);
         return startDate.format2445();
@@ -237,8 +239,8 @@
         }
 
         final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
-        builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP,
-                Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong));
+        builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP, Arrays.asList(callLogTypeStr),
+                toRfc2455Format(dateAsLong));
     }
 
     public void terminate() {
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapConfig.java b/src/com/android/bluetooth/pbap/BluetoothPbapConfig.java
index ca3f565..28051a6 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapConfig.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapConfig.java
@@ -17,27 +17,28 @@
  ************************************************************************************/
 package com.android.bluetooth.pbap;
 
-import com.android.bluetooth.R;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.bluetooth.R;
+
 public class BluetoothPbapConfig {
-    private static boolean sUseProfileForOwnerVcard=true;
+    private static boolean sUseProfileForOwnerVcard = true;
     private static boolean sIncludePhotosInVcard = false;
+
     public static void init(Context ctx) {
         Resources r = ctx.getResources();
         if (r != null) {
             try {
                 sUseProfileForOwnerVcard = r.getBoolean(R.bool.pbap_use_profile_for_owner_vcard);
-            } catch(Exception e) {
-                Log.e("BluetoothPbapConfig","",e);
+            } catch (Exception e) {
+                Log.e("BluetoothPbapConfig", "", e);
             }
             try {
                 sIncludePhotosInVcard = r.getBoolean(R.bool.pbap_include_photos_in_vcard);
-            } catch(Exception e) {
-                Log.e("BluetoothPbapConfig","",e);
+            } catch (Exception e) {
+                Log.e("BluetoothPbapConfig", "", e);
             }
         }
     }
@@ -46,7 +47,7 @@
      * If true, owner vcard will be generated from the "Me" profile
      */
     public static boolean useProfileForOwnerVcard() {
-        return sUseProfileForOwnerVcard;        
+        return sUseProfileForOwnerVcard;
     }
 
     /**
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
index 01f8be9..247a76d 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -32,30 +32,31 @@
 
 package com.android.bluetooth.pbap;
 
-import android.content.Context;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.database.Cursor;
-import android.os.Message;
 import android.os.Handler;
+import android.os.Message;
+import android.os.UserManager;
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
 import android.text.TextUtils;
 import android.util.Log;
-import android.os.SystemProperties;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.ByteBuffer;
 import java.text.CharacterIterator;
 import java.text.StringCharacterIterator;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.nio.ByteBuffer;
+import java.util.Collections;
 
-import javax.obex.ServerRequestHandler;
-import javax.obex.ResponseCodes;
 import javax.obex.ApplicationParameter;
-import javax.obex.Operation;
 import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+import javax.obex.ServerRequestHandler;
 
 public class BluetoothPbapObexServer extends ServerRequestHandler {
 
@@ -73,22 +74,49 @@
     private static final int VCARD_NAME_SUFFIX_LENGTH = 5;
 
     // 128 bit UUID for PBAP
-    private static final byte[] PBAP_TARGET = new byte[] {
-            0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66,
-            0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66
+    private static final byte[] PBAP_TARGET = new byte[]{
+            0x79,
+            0x61,
+            0x35,
+            (byte) 0xf0,
+            (byte) 0xf0,
+            (byte) 0xc5,
+            0x11,
+            (byte) 0xd8,
+            0x09,
+            0x66,
+            0x08,
+            0x00,
+            0x20,
+            0x0c,
+            (byte) 0x9a,
+            0x66
     };
 
     // Currently not support SIM card
     private static final String[] LEGAL_PATH = {
-            "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
+            "/telecom",
+            "/telecom/pb",
+            "/telecom/ich",
+            "/telecom/och",
+            "/telecom/mch",
             "/telecom/cch"
     };
 
-    @SuppressWarnings("unused")
-    private static final String[] LEGAL_PATH_WITH_SIM = {
-            "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
-            "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och",
-            "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb"
+    @SuppressWarnings("unused") private static final String[] LEGAL_PATH_WITH_SIM = {
+            "/telecom",
+            "/telecom/pb",
+            "/telecom/ich",
+            "/telecom/och",
+            "/telecom/mch",
+            "/telecom/cch",
+            "/SIM1",
+            "/SIM1/telecom",
+            "/SIM1/telecom/ich",
+            "/SIM1/telecom/och",
+            "/SIM1/telecom/mch",
+            "/SIM1/telecom/cch",
+            "/SIM1/telecom/pb"
 
     };
 
@@ -143,8 +171,6 @@
 
     private boolean mVcardSelector = false;
 
-    private int mMissedCallSize = 0;
-
     // record current path the client are browsing
     private String mCurrentPath = "";
 
@@ -152,17 +178,15 @@
 
     private Context mContext;
 
-    private BluetoothPbapService mService;
-
     private BluetoothPbapVcardManager mVcardManager;
 
-    private int mOrderBy  = ORDER_BY_INDEXED;
+    private int mOrderBy = ORDER_BY_INDEXED;
 
-    private static int CALLLOG_NUM_LIMIT = 50;
+    private static final int CALLLOG_NUM_LIMIT = 50;
 
-    public static int ORDER_BY_INDEXED = 0;
+    public static final int ORDER_BY_INDEXED = 0;
 
-    public static int ORDER_BY_ALPHABETICAL = 1;
+    public static final int ORDER_BY_ALPHABETICAL = 1;
 
     public static boolean sIsAborted = false;
 
@@ -170,11 +194,13 @@
 
     private long mDatabaseIdentifierHigh = INVALID_VALUE_PARAMETER;
 
-    private long folderVersionCounterbitMask = 0x0008;
+    private long mFolderVersionCounterbitMask = 0x0008;
 
-    private long databaseIdentifierBitMask = 0x0004;
+    private long mDatabaseIdentifierBitMask = 0x0004;
 
-    private AppParamValue connAppParamValue;
+    private AppParamValue mConnAppParamValue;
+
+    private PbapStateMachine mStateMachine;
 
     public static class ContentType {
         public static final int PHONEBOOK = 1;
@@ -188,24 +214,29 @@
         public static final int COMBINED_CALL_HISTORY = 5;
     }
 
-    public BluetoothPbapObexServer(Handler callback, BluetoothPbapService service) {
+    public BluetoothPbapObexServer(Handler callback, Context context,
+            PbapStateMachine stateMachine) {
         super();
         mCallback = callback;
-        mService = service;
-        mContext = service.getApplicationContext();
+        mContext = context;
         mVcardManager = new BluetoothPbapVcardManager(mContext);
+        mStateMachine = stateMachine;
     }
 
     @Override
     public int onConnect(final HeaderSet request, HeaderSet reply) {
-        if (V) logHeader(request);
+        if (V) {
+            logHeader(request);
+        }
         notifyUpdateWakeLock();
         try {
-            byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
+            byte[] uuid = (byte[]) request.getHeader(HeaderSet.TARGET);
             if (uuid == null) {
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
             }
-            if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
+            if (D) {
+                Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
+            }
 
             if (uuid.length != UUID_LENGTH) {
                 Log.w(TAG, "Wrong UUID length");
@@ -224,9 +255,11 @@
         }
 
         try {
-            byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
+            byte[] remote = (byte[]) request.getHeader(HeaderSet.WHO);
             if (remote != null) {
-                if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
+                if (D) {
+                    Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
+                }
                 reply.setHeader(HeaderSet.TARGET, remote);
             }
         } catch (IOException e) {
@@ -236,9 +269,9 @@
 
         try {
             byte[] appParam = null;
-            connAppParamValue = new AppParamValue();
+            mConnAppParamValue = new AppParamValue();
             appParam = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER);
-            if ((appParam != null) && !parseApplicationParameter(appParam, connAppParamValue)) {
+            if ((appParam != null) && !parseApplicationParameter(appParam, mConnAppParamValue)) {
                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
             }
         } catch (IOException e) {
@@ -246,32 +279,30 @@
             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
         }
 
-        if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
-                "MSG_SESSION_ESTABLISHED msg.");
+        if (V) {
+            Log.v(TAG, "onConnect(): uuid is ok, will send out " + "MSG_SESSION_ESTABLISHED msg.");
+        }
 
-        Message msg = Message.obtain(mCallback);
-        msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED;
-        msg.sendToTarget();
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
     @Override
     public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
-        if (D) Log.d(TAG, "onDisconnect(): enter");
-        if (V) logHeader(req);
+        if (D) {
+            Log.d(TAG, "onDisconnect(): enter");
+        }
+        if (V) {
+            logHeader(req);
+        }
         notifyUpdateWakeLock();
         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
-        if (mCallback != null) {
-            Message msg = Message.obtain(mCallback);
-            msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED;
-            msg.sendToTarget();
-            if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
-        }
     }
 
     @Override
     public int onAbort(HeaderSet request, HeaderSet reply) {
-        if (D) Log.d(TAG, "onAbort(): enter.");
+        if (D) {
+            Log.d(TAG, "onAbort(): enter.");
+        }
         notifyUpdateWakeLock();
         sIsAborted = true;
         return ResponseCodes.OBEX_HTTP_OK;
@@ -279,14 +310,18 @@
 
     @Override
     public int onPut(final Operation op) {
-        if (D) Log.d(TAG, "onPut(): not support PUT request.");
+        if (D) {
+            Log.d(TAG, "onPut(): not support PUT request.");
+        }
         notifyUpdateWakeLock();
         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
     }
 
     @Override
     public int onDelete(final HeaderSet request, final HeaderSet reply) {
-        if (D) Log.d(TAG, "onDelete(): not support PUT request.");
+        if (D) {
+            Log.d(TAG, "onDelete(): not support PUT request.");
+        }
         notifyUpdateWakeLock();
         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
     }
@@ -294,33 +329,38 @@
     @Override
     public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
             final boolean create) {
-        if (V) logHeader(request);
-        if (D) Log.d(TAG, "before setPath, mCurrentPath ==  " + mCurrentPath);
+        if (V) {
+            logHeader(request);
+        }
+        if (D) {
+            Log.d(TAG, "before setPath, mCurrentPath ==  " + mCurrentPath);
+        }
         notifyUpdateWakeLock();
-        String current_path_tmp = mCurrentPath;
-        String tmp_path = null;
+        String currentPathTmp = mCurrentPath;
+        String tmpPath = null;
         try {
-            tmp_path = (String)request.getHeader(HeaderSet.NAME);
+            tmpPath = (String) request.getHeader(HeaderSet.NAME);
         } catch (IOException e) {
             Log.e(TAG, "Get name header fail");
             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
         }
-        if (D) Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmp_path);
+        if (D) {
+            Log.d(TAG, "backup=" + backup + " create=" + create + " name=" + tmpPath);
+        }
 
         if (backup) {
-            if (current_path_tmp.length() != 0) {
-                current_path_tmp = current_path_tmp.substring(0,
-                        current_path_tmp.lastIndexOf("/"));
+            if (currentPathTmp.length() != 0) {
+                currentPathTmp = currentPathTmp.substring(0, currentPathTmp.lastIndexOf("/"));
             }
         } else {
-            if (tmp_path == null) {
-                current_path_tmp = "";
+            if (tmpPath == null) {
+                currentPathTmp = "";
             } else {
-                current_path_tmp = current_path_tmp + "/" + tmp_path;
+                currentPathTmp = currentPathTmp + "/" + tmpPath;
             }
         }
 
-        if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) {
+        if ((currentPathTmp.length() != 0) && (!isLegalPath(currentPathTmp))) {
             if (create) {
                 Log.w(TAG, "path create is forbidden!");
                 return ResponseCodes.OBEX_HTTP_FORBIDDEN;
@@ -329,20 +369,17 @@
                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
             }
         }
-        mCurrentPath = current_path_tmp;
-        if (V) Log.v(TAG, "after setPath, mCurrentPath ==  " + mCurrentPath);
+        mCurrentPath = currentPathTmp;
+        if (V) {
+            Log.v(TAG, "after setPath, mCurrentPath ==  " + mCurrentPath);
+        }
 
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
     @Override
     public void onClose() {
-        if (mCallback != null) {
-            Message msg = Message.obtain(mCallback);
-            msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE;
-            msg.sendToTarget();
-            if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
-        }
+        mStateMachine.sendMessage(PbapStateMachine.DISCONNECT);
     }
 
     @Override
@@ -357,9 +394,9 @@
         AppParamValue appParamValue = new AppParamValue();
         try {
             request = op.getReceivedHeader();
-            type = (String)request.getHeader(HeaderSet.TYPE);
-            name = (String)request.getHeader(HeaderSet.NAME);
-            appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
+            type = (String) request.getHeader(HeaderSet.TYPE);
+            name = (String) request.getHeader(HeaderSet.NAME);
+            appParam = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER);
         } catch (IOException e) {
             Log.e(TAG, "request headers error");
             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
@@ -367,12 +404,22 @@
 
         /* TODO: block Get request if contacts are not completely loaded locally */
 
-        if (V) logHeader(request);
-        if (D) Log.d(TAG, "OnGet type is " + type + "; name is " + name);
+        if (V) {
+            logHeader(request);
+        }
+        if (D) {
+            Log.d(TAG, "OnGet type is " + type + "; name is " + name);
+        }
 
         if (type == null) {
             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
         }
+
+        if (!UserManager.get(mContext).isUserUnlocked()) {
+            Log.e(TAG, "Storage locked, " + type + " failed");
+            return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
+        }
+
         // Accroding to specification,the name header could be omitted such as
         // sony erriccsonHBH-DS980
 
@@ -387,8 +434,10 @@
         }
 
         if (!validName || (validName && type.equals(TYPE_VCARD))) {
-            if (D) Log.d(TAG, "Guess what carkit actually want from current path (" +
-                    mCurrentPath + ")");
+            if (D) {
+                Log.d(TAG,
+                        "Guess what carkit actually want from current path (" + mCurrentPath + ")");
+            }
 
             if (mCurrentPath.equals(PB_PATH)) {
                 appParamValue.needTag = ContentType.PHONEBOOK;
@@ -411,7 +460,9 @@
                 Log.w(TAG, "mCurrentpath is not valid path!!!");
                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
             }
-            if (D) Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag);
+            if (D) {
+                Log.v(TAG, "onGet(): appParamValue.needTag=" + appParamValue.needTag);
+            }
         } else {
             // Not support SIM card currently
             if (name.contains(SIM1.subSequence(0, SIM1.length()))) {
@@ -424,32 +475,42 @@
             // "pb.vcf" is required by SIG spec.
             if (isNameMatchTarget(name, PB)) {
                 appParamValue.needTag = ContentType.PHONEBOOK;
-                if (D) Log.v(TAG, "download phonebook request");
+                if (D) {
+                    Log.v(TAG, "download phonebook request");
+                }
             } else if (isNameMatchTarget(name, ICH)) {
                 appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY;
                 appParamValue.callHistoryVersionCounter =
                         mVcardManager.getCallHistoryPrimaryFolderVersion(
                                 ContentType.INCOMING_CALL_HISTORY);
-                if (D) Log.v(TAG, "download incoming calls request");
+                if (D) {
+                    Log.v(TAG, "download incoming calls request");
+                }
             } else if (isNameMatchTarget(name, OCH)) {
                 appParamValue.needTag = ContentType.OUTGOING_CALL_HISTORY;
                 appParamValue.callHistoryVersionCounter =
                         mVcardManager.getCallHistoryPrimaryFolderVersion(
                                 ContentType.OUTGOING_CALL_HISTORY);
-                if (D) Log.v(TAG, "download outgoing calls request");
+                if (D) {
+                    Log.v(TAG, "download outgoing calls request");
+                }
             } else if (isNameMatchTarget(name, MCH)) {
                 appParamValue.needTag = ContentType.MISSED_CALL_HISTORY;
                 appParamValue.callHistoryVersionCounter =
                         mVcardManager.getCallHistoryPrimaryFolderVersion(
                                 ContentType.MISSED_CALL_HISTORY);
                 mNeedNewMissedCallsNum = true;
-                if (D) Log.v(TAG, "download missed calls request");
+                if (D) {
+                    Log.v(TAG, "download missed calls request");
+                }
             } else if (isNameMatchTarget(name, CCH)) {
                 appParamValue.needTag = ContentType.COMBINED_CALL_HISTORY;
                 appParamValue.callHistoryVersionCounter =
                         mVcardManager.getCallHistoryPrimaryFolderVersion(
                                 ContentType.COMBINED_CALL_HISTORY);
-                if (D) Log.v(TAG, "download combined calls request");
+                if (D) {
+                    Log.v(TAG, "download combined calls request");
+                }
             } else {
                 Log.w(TAG, "Input name doesn't contain valid info!!!");
                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
@@ -463,13 +524,11 @@
         // listing request
         if (type.equals(TYPE_LISTING)) {
             return pullVcardListing(appParam, appParamValue, reply, op, name);
-        }
-        // pull vcard entry request
-        else if (type.equals(TYPE_VCARD)) {
+        } else if (type.equals(TYPE_VCARD)) {
+            // pull vcard entry request
             return pullVcardEntry(appParam, appParamValue, op, reply, name, mCurrentPath);
-        }
-        // down load phone book request
-        else if (type.equals(TYPE_PB)) {
+        } else if (type.equals(TYPE_PB)) {
+            // down load phone book request
             return pullPhonebook(appParam, appParamValue, reply, op, name);
         } else {
             Log.w(TAG, "unknown type request!!!");
@@ -478,11 +537,13 @@
     }
 
     private boolean isNameMatchTarget(String name, String target) {
-        if (name == null) return false;
+        if (name == null) {
+            return false;
+        }
         String contentTypeName = name;
         if (contentTypeName.endsWith(".vcf")) {
-            contentTypeName = contentTypeName
-                    .substring(0, contentTypeName.length() - ".vcf".length());
+            contentTypeName =
+                    contentTypeName.substring(0, contentTypeName.length() - ".vcf".length());
         }
         // There is a test case: Client will send a wrong name "/telecom/pbpb".
         // So we must use the String between '/' and '/' as a indivisible part
@@ -497,7 +558,7 @@
     }
 
     /** check whether path is legal */
-    private final boolean isLegalPath(final String str) {
+    private boolean isLegalPath(final String str) {
         if (str.length() == 0) {
             return true;
         }
@@ -542,7 +603,7 @@
 
         public byte[] callHistoryVersionCounter;
 
-        public AppParamValue() {
+        AppParamValue() {
             maxListCount = 0xFFFF;
             listStartOffset = 0;
             searchValue = "";
@@ -553,33 +614,31 @@
             //Filter is not set by default
             ignorefilter = true;
             vCardSelectorOperator = "0";
-            propertySelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-            vCardSelector = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-            supportedFeature = new byte[] {0x00, 0x00, 0x00, 0x00};
+            propertySelector = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+            vCardSelector = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+            supportedFeature = new byte[]{0x00, 0x00, 0x00, 0x00};
         }
 
         public void dump() {
             Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset
-                            + " searchValue=" + searchValue + " searchAttr=" + searchAttr
-                            + " needTag=" + needTag + " vcard21=" + vcard21 + " order=" + order
-                            + "vcardselector=" + vCardSelector + "vcardselop="
-                            + vCardSelectorOperator);
+                    + " searchValue=" + searchValue + " searchAttr=" + searchAttr + " needTag="
+                    + needTag + " vcard21=" + vcard21 + " order=" + order + "vcardselector="
+                    + vCardSelector + "vcardselop=" + vCardSelectorOperator);
         }
     }
 
     /** To parse obex application parameter */
-    private final boolean parseApplicationParameter(final byte[] appParam,
-            AppParamValue appParamValue) {
+    private boolean parseApplicationParameter(final byte[] appParam, AppParamValue appParamValue) {
         int i = 0;
         boolean parseOk = true;
-        while ((i < appParam.length) && (parseOk == true)) {
+        while ((i < appParam.length) && (parseOk)) {
             switch (appParam[i]) {
                 case ApplicationParameter.TRIPLET_TAGID.PROPERTY_SELECTOR_TAGID:
                     i += 2; // length and tag field in triplet
                     for (int index = 0;
                             index < ApplicationParameter.TRIPLET_LENGTH.PROPERTY_SELECTOR_LENGTH;
                             index++) {
-                        if (appParam[i+index] != 0){
+                        if (appParam[i + index] != 0) {
                             appParamValue.ignorefilter = false;
                             appParamValue.propertySelector[index] = appParam[i + index];
                         }
@@ -612,8 +671,8 @@
                         parseOk = false;
                         break;
                     }
-                    if (appParam[i+length] == 0x0) {
-                        appParamValue.searchValue = new String(appParam, i + 1, length-1);
+                    if (appParam[i + length] == 0x0) {
+                        appParamValue.searchValue = new String(appParam, i + 1, length - 1);
                     } else {
                         appParamValue.searchValue = new String(appParam, i + 1, length);
                     }
@@ -644,7 +703,7 @@
                     i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
                     break;
                 case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
-                    i += 2;// length field in triplet
+                    i += 2; // length field in triplet
                     if (appParam[i] != 0) {
                         appParamValue.vcard21 = false;
                     }
@@ -675,14 +734,16 @@
             }
         }
 
-        if (D) appParamValue.dump();
+        if (D) {
+            appParamValue.dump();
+        }
 
         return parseOk;
     }
 
     /** Form and Send an XML format String to client for Phone book listing */
-    private final int sendVcardListingXml(
-            AppParamValue appParamValue, Operation op, int needSendBody, int size) {
+    private int sendVcardListingXml(AppParamValue appParamValue, Operation op, int needSendBody,
+            int size) {
         StringBuilder result = new StringBuilder();
         int itemsFound = 0;
         result.append("<?xml version=\"1.0\"?>");
@@ -702,29 +763,31 @@
             } else {
                 return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
             }
-        }
-        // Call history listing request
-        else {
+        } else {
+            // Call history listing request
             ArrayList<String> nameList = mVcardManager.loadCallHistoryList(appParamValue.needTag);
-            int requestSize = nameList.size() >= appParamValue.maxListCount
-                    ? appParamValue.maxListCount
-                    : nameList.size();
+            int requestSize =
+                    nameList.size() >= appParamValue.maxListCount ? appParamValue.maxListCount
+                            : nameList.size();
             int startPoint = appParamValue.listStartOffset;
             int endPoint = startPoint + requestSize;
             if (endPoint > nameList.size()) {
                 endPoint = nameList.size();
             }
-            if (D)
+            if (D) {
                 Log.d(TAG, "call log list, size=" + requestSize + " offset="
-                                + appParamValue.listStartOffset);
+                        + appParamValue.listStartOffset);
+            }
 
             for (int j = startPoint; j < endPoint; j++) {
-                writeVCardEntry(j+1, nameList.get(j),result);
+                writeVCardEntry(j + 1, nameList.get(j), result);
             }
         }
         result.append("</vCard-listing>");
 
-        if (D) Log.d(TAG, "itemsFound =" + itemsFound);
+        if (D) {
+            Log.d(TAG, "itemsFound =" + itemsFound);
+        }
 
         return pushBytes(op, result.toString());
     }
@@ -742,33 +805,39 @@
             nameList = mVcardManager.getPhonebookNameList(mOrderBy);
         }
 
-        final int requestSize = nameList.size() >= appParamValue.maxListCount
-                ? appParamValue.maxListCount
-                : nameList.size();
+        final int requestSize =
+                nameList.size() >= appParamValue.maxListCount ? appParamValue.maxListCount
+                        : nameList.size();
         final int listSize = nameList.size();
         String compareValue = "", currentValue;
 
-        if (D)
+        if (D) {
             Log.d(TAG, "search by " + type + ", requestSize=" + requestSize + " offset="
-                            + appParamValue.listStartOffset + " searchValue="
-                            + appParamValue.searchValue);
+                    + appParamValue.listStartOffset + " searchValue=" + appParamValue.searchValue);
+        }
 
         if (type.equals("number")) {
             // 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);
+                if (D) {
+                    Log.d(TAG, "compareValue=" + compareValue);
+                }
                 for (int pos = appParamValue.listStartOffset;
                         pos < listSize && itemsFound < requestSize; pos++) {
                     currentValue = nameList.get(pos);
-                    if (V) Log.d(TAG, "currentValue=" + currentValue);
+                    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);
+                        if (currentValue.contains(",")) {
+                            currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
+                        }
+                        writeVCardEntry(pos, currentValue, result);
                     }
                 }
                 if (itemsFound >= requestSize) {
@@ -782,13 +851,14 @@
             for (int pos = appParamValue.listStartOffset;
                     pos < listSize && itemsFound < requestSize; pos++) {
                 currentValue = nameList.get(pos);
-                if (currentValue.contains(","))
+                if (currentValue.contains(",")) {
                     currentValue = currentValue.substring(0, currentValue.lastIndexOf(','));
+                }
 
-                if (appParamValue.searchValue.isEmpty()
-                        || ((currentValue.toLowerCase()).startsWith(compareValue))) {
+                if (appParamValue.searchValue.isEmpty() || ((currentValue.toLowerCase()).startsWith(
+                        compareValue))) {
                     itemsFound++;
-                    writeVCardEntry(pos, currentValue,result);
+                    writeVCardEntry(pos, currentValue, result);
                 }
             }
         }
@@ -799,11 +869,15 @@
      * Function to send obex header back to client such as get phonebook size
      * request
      */
-    private final int pushHeader(final Operation op, final HeaderSet reply) {
+    private int pushHeader(final Operation op, final HeaderSet reply) {
         OutputStream outputStream = null;
 
-        if (D) Log.d(TAG, "Push Header");
-        if (D) Log.d(TAG, reply.toString());
+        if (D) {
+            Log.d(TAG, "Push Header");
+        }
+        if (D) {
+            Log.d(TAG, reply.toString());
+        }
 
         int pushResult = ResponseCodes.OBEX_HTTP_OK;
         try {
@@ -822,7 +896,7 @@
     }
 
     /** Function to send vcard data to client */
-    private final int pushBytes(Operation op, final String vcardString) {
+    private int pushBytes(Operation op, final String vcardString) {
         if (vcardString == null) {
             Log.w(TAG, "vcardString is null!");
             return ResponseCodes.OBEX_HTTP_OK;
@@ -833,7 +907,9 @@
         try {
             outputStream = op.openOutputStream();
             outputStream.write(vcardString.getBytes());
-            if (V) Log.v(TAG, "Send Data complete!");
+            if (V) {
+                Log.v(TAG, "Send Data complete!");
+            }
         } catch (IOException e) {
             Log.e(TAG, "open/write outputstrem failed" + e.toString());
             pushResult = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
@@ -846,29 +922,34 @@
         return pushResult;
     }
 
-    private final int handleAppParaForResponse(
-            AppParamValue appParamValue, int size, HeaderSet reply, Operation op, String name) {
+    private int handleAppParaForResponse(AppParamValue appParamValue, int size, HeaderSet reply,
+            Operation op, String name) {
         byte[] misnum = new byte[1];
         ApplicationParameter ap = new ApplicationParameter();
         boolean needSendCallHistoryVersionCounters = false;
-        if (isNameMatchTarget(name, MCH) || isNameMatchTarget(name, ICH)
-                || isNameMatchTarget(name, OCH) || isNameMatchTarget(name, CCH))
+        if (isNameMatchTarget(name, MCH) || isNameMatchTarget(name, ICH) || isNameMatchTarget(name,
+                OCH) || isNameMatchTarget(name, CCH)) {
             needSendCallHistoryVersionCounters =
-                    checkPbapFeatureSupport(folderVersionCounterbitMask);
+                    checkPbapFeatureSupport(mFolderVersionCounterbitMask);
+        }
         boolean needSendPhonebookVersionCounters = false;
-        if (isNameMatchTarget(name, PB))
-            needSendPhonebookVersionCounters = checkPbapFeatureSupport(folderVersionCounterbitMask);
+        if (isNameMatchTarget(name, PB)) {
+            needSendPhonebookVersionCounters =
+                    checkPbapFeatureSupport(mFolderVersionCounterbitMask);
+        }
 
         // In such case, PCE only want the number of index.
         // So response not contain any Body header.
         if (mNeedPhonebookSize) {
-            if (D) Log.d(TAG, "Need Phonebook size in response header.");
+            if (D) {
+                Log.d(TAG, "Need Phonebook size in response header.");
+            }
             mNeedPhonebookSize = false;
 
             byte[] pbsize = new byte[2];
 
-            pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
-            pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
+            pbsize[0] = (byte) ((size / 256) & 0xff); // HIGH VALUE
+            pbsize[1] = (byte) ((size % 256) & 0xff); // LOW VALUE
             ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
                     ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
 
@@ -878,24 +959,25 @@
                 ContentResolver contentResolver;
                 contentResolver = mContext.getContentResolver();
 
-                Cursor c = contentResolver.query(
-                    Calls.CONTENT_URI,
-                    null,
-                    Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1",
-                    null,
-                    Calls.DEFAULT_SORT_ORDER);
+                Cursor c = contentResolver.query(Calls.CONTENT_URI, null,
+                        Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND "
+                                + android.provider.CallLog.Calls.NEW + " = 1", null,
+                        Calls.DEFAULT_SORT_ORDER);
 
                 if (c != null) {
-                  nmnum = c.getCount();
-                  c.close();
+                    nmnum = c.getCount();
+                    c.close();
                 }
 
                 nmnum = nmnum > 0 ? nmnum : 0;
-                misnum[0] = (byte)nmnum;
-                if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
+                misnum[0] = (byte) nmnum;
+                if (D) {
+                    Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
+                            + nmnum);
+                }
             }
 
-            if (checkPbapFeatureSupport(databaseIdentifierBitMask)) {
+            if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
                 setDbCounters(ap);
             }
             if (needSendPhonebookVersionCounters) {
@@ -906,7 +988,9 @@
             }
             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
 
-            if (D) Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
+            if (D) {
+                Log.d(TAG, "Send back Phonebook size only, without body info! Size= " + size);
+            }
 
             return pushHeader(op, reply);
         }
@@ -915,33 +999,38 @@
         // NewMissedCalls is used only in the response, together with Body
         // header.
         if (mNeedNewMissedCallsNum) {
-            if (D) Log.d(TAG, "Need new missed call num in response header.");
+            if (D) {
+                Log.d(TAG, "Need new missed call num in response header.");
+            }
             mNeedNewMissedCallsNum = false;
             int nmnum = 0;
             ContentResolver contentResolver;
             contentResolver = mContext.getContentResolver();
 
-            Cursor c = contentResolver.query(
-                Calls.CONTENT_URI,
-                null,
-                Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND " + android.provider.CallLog.Calls.NEW + " = 1",
-                null,
-                Calls.DEFAULT_SORT_ORDER);
+            Cursor c = contentResolver.query(Calls.CONTENT_URI, null,
+                    Calls.TYPE + " = " + Calls.MISSED_TYPE + " AND "
+                            + android.provider.CallLog.Calls.NEW + " = 1", null,
+                    Calls.DEFAULT_SORT_ORDER);
 
             if (c != null) {
-              nmnum = c.getCount();
-              c.close();
+                nmnum = c.getCount();
+                c.close();
             }
 
             nmnum = nmnum > 0 ? nmnum : 0;
-            misnum[0] = (byte)nmnum;
-            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
+            misnum[0] = (byte) nmnum;
+            if (D) {
+                Log.d(TAG,
+                        "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
+            }
 
             ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
                     ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
-            if (D) Log.d(TAG, "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= "
-                        + nmnum);
+            if (D) {
+                Log.d(TAG,
+                        "handleAppParaForResponse(): mNeedNewMissedCallsNum=true,  num= " + nmnum);
+            }
 
             // Only Specifies the headers, not write for now, will write to PCE
             // together with Body
@@ -953,7 +1042,7 @@
             }
         }
 
-        if (checkPbapFeatureSupport(databaseIdentifierBitMask)) {
+        if (checkPbapFeatureSupport(mDatabaseIdentifierBitMask)) {
             setDbCounters(ap);
             reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
             try {
@@ -989,14 +1078,16 @@
         return NEED_SEND_BODY;
     }
 
-    private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
-            HeaderSet reply, Operation op, String name) {
+    private int pullVcardListing(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
+            Operation op, String name) {
         String searchAttr = appParamValue.searchAttr.trim();
 
         if (searchAttr == null || searchAttr.length() == 0) {
             // If searchAttr is not set by PCE, set default value per spec.
             appParamValue.searchAttr = "0";
-            if (D) Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
+            if (D) {
+                Log.d(TAG, "searchAttr is not set by PCE, assume search by name by default");
+            }
         } else if (!searchAttr.equals("0") && !searchAttr.equals("1")) {
             Log.w(TAG, "search attr not supported");
             if (searchAttr.equals("2")) {
@@ -1017,7 +1108,9 @@
         }
 
         if (size == 0) {
-            if (D) Log.d(TAG, "PhonebookSize is 0, return.");
+            if (D) {
+                Log.d(TAG, "PhonebookSize is 0, return.");
+            }
             return ResponseCodes.OBEX_HTTP_OK;
         }
 
@@ -1025,10 +1118,14 @@
         if (TextUtils.isEmpty(orderPara)) {
             // If order parameter is not set by PCE, set default value per spec.
             orderPara = "0";
-            if (D) Log.d(TAG, "Order parameter is not set by PCE. " +
-                       "Assume order by 'Indexed' by default");
+            if (D) {
+                Log.d(TAG, "Order parameter is not set by PCE. "
+                        + "Assume order by 'Indexed' by default");
+            }
         } else if (!orderPara.equals("0") && !orderPara.equals("1")) {
-            if (D) Log.d(TAG, "Order parameter is not supported: " + appParamValue.order);
+            if (D) {
+                Log.d(TAG, "Order parameter is not supported: " + appParamValue.order);
+            }
             if (orderPara.equals("2")) {
                 // Order by sound is not supported currently
                 Log.w(TAG, "Do not support order by sound");
@@ -1048,10 +1145,12 @@
         return sendVcardListingXml(appParamValue, op, needSendBody, size);
     }
 
-    private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op,
-            HeaderSet reply, final String name, final String current_path) {
+    private int pullVcardEntry(byte[] appParam, AppParamValue appParamValue, Operation op,
+            HeaderSet reply, final String name, final String currentPath) {
         if (name == null || name.length() < VCARD_NAME_SUFFIX_LENGTH) {
-            if (D) Log.d(TAG, "Name is Null, or the length of name < 5 !");
+            if (D) {
+                Log.d(TAG, "Name is Null, or the length of name < 5 !");
+            }
             return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
         }
         String strIndex = name.substring(0, name.length() - VCARD_NAME_SUFFIX_LENGTH + 1);
@@ -1068,7 +1167,9 @@
         int size = mVcardManager.getPhonebookSize(appParamValue.needTag);
         int needSendBody = handleAppParaForResponse(appParamValue, size, reply, op, name);
         if (size == 0) {
-            if (D) Log.d(TAG, "PhonebookSize is 0, return.");
+            if (D) {
+                Log.d(TAG, "PhonebookSize is 0, return.");
+            }
             return ResponseCodes.OBEX_HTTP_NOT_FOUND;
         }
 
@@ -1082,7 +1183,8 @@
                 return ResponseCodes.OBEX_HTTP_NOT_FOUND;
             } else if (intIndex == 0) {
                 // For PB_PATH, 0.vcf is the phone number of this phone.
-                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null);
+                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,
+                        appParamValue.ignorefilter ? null : appParamValue.propertySelector);
                 return pushBytes(op, ownerVcard);
             } else {
                 return mVcardManager.composeAndSendPhonebookOneVcard(op, intIndex, vcard21, null,
@@ -1105,14 +1207,14 @@
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
-    private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
+    private int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
             Operation op, final String name) {
         // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
         if (name != null) {
             int dotIndex = name.indexOf(".");
             String vcf = "vcf";
             if (dotIndex >= 0 && dotIndex <= name.length()) {
-                if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
+                if (!name.regionMatches(dotIndex + 1, vcf, 0, vcf.length())) {
                     Log.w(TAG, "name is not .vcf");
                     return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
                 }
@@ -1127,12 +1229,14 @@
         }
 
         if (pbSize == 0) {
-            if (D) Log.d(TAG, "PhonebookSize is 0, return.");
+            if (D) {
+                Log.d(TAG, "PhonebookSize is 0, return.");
+            }
             return ResponseCodes.OBEX_HTTP_OK;
         }
 
-        int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount
-                : pbSize;
+        int requestSize =
+                pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
         int startPoint = appParamValue.listStartOffset;
         if (startPoint < 0 || startPoint >= pbSize) {
             Log.w(TAG, "listStartOffset is not correct! " + startPoint);
@@ -1142,7 +1246,7 @@
         // Limit the number of call log to CALLLOG_NUM_LIMIT
         if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) {
             if (requestSize > CALLLOG_NUM_LIMIT) {
-               requestSize = CALLLOG_NUM_LIMIT;
+                requestSize = CALLLOG_NUM_LIMIT;
             }
         }
 
@@ -1150,13 +1254,16 @@
         if (endPoint > pbSize - 1) {
             endPoint = pbSize - 1;
         }
-        if (D) Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" +
-                startPoint + " endPoint=" + endPoint);
+        if (D) {
+            Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + startPoint
+                    + " endPoint=" + endPoint);
+        }
 
         boolean vcard21 = appParamValue.vcard21;
         if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) {
             if (startPoint == 0) {
-                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, null);
+                String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21,
+                        appParamValue.ignorefilter ? null : appParamValue.propertySelector);
                 if (endPoint == 0) {
                     return pushBytes(op, ownerVcard);
                 } else {
@@ -1203,6 +1310,7 @@
 
     // Reserved for future use. In case PSE challenge PCE and PCE input wrong
     // session key.
+    @Override
     public final void onAuthenticationFailure(final byte[] userName) {
     }
 
@@ -1210,8 +1318,9 @@
         String selection = null;
         switch (type) {
             case ContentType.INCOMING_CALL_HISTORY:
-                selection = "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR "
-                        + Calls.TYPE + "=" + CallLog.Calls.REJECTED_TYPE + ")";
+                selection =
+                        "(" + Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE + " OR " + Calls.TYPE
+                                + "=" + CallLog.Calls.REJECTED_TYPE + ")";
                 break;
             case ContentType.OUTGOING_CALL_HISTORY:
                 selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
@@ -1222,7 +1331,9 @@
             default:
                 break;
         }
-        if (V) Log.v(TAG, "Call log selection: " + selection);
+        if (V) {
+            Log.v(TAG, "Call log selection: " + selection);
+        }
         return selection;
     }
 
@@ -1235,24 +1346,19 @@
         }
 
         final StringCharacterIterator iterator = new StringCharacterIterator(name);
-        char character =  iterator.current();
-        while (character != CharacterIterator.DONE ){
+        char character = iterator.current();
+        while (character != CharacterIterator.DONE) {
             if (character == '<') {
                 result.append("&lt;");
-            }
-            else if (character == '>') {
+            } else if (character == '>') {
                 result.append("&gt;");
-            }
-            else if (character == '\"') {
+            } else if (character == '\"') {
                 result.append("&quot;");
-            }
-            else if (character == '\'') {
+            } else if (character == '\'') {
                 result.append("&#039;");
-            }
-            else if (character == '&') {
+            } else if (character == '&') {
                 result.append("&amp;");
-            }
-            else {
+            } else {
                 // The char is not a special one, add it to the result as is
                 result.append(character);
             }
@@ -1322,7 +1428,7 @@
 
     private byte[] getDatabaseIdentifier() {
         mDatabaseIdentifierHigh = 0;
-        mDatabaseIdentifierLow = mService.getDbIdentifier();
+        mDatabaseIdentifierLow = BluetoothPbapUtils.sDbIdentifier.get();
         if (mDatabaseIdentifierLow != INVALID_VALUE_PARAMETER
                 && mDatabaseIdentifierHigh != INVALID_VALUE_PARAMETER) {
             ByteBuffer ret = ByteBuffer.allocate(16);
@@ -1339,8 +1445,8 @@
         ByteBuffer pvc = ByteBuffer.allocate(16);
         pvc.putLong(primaryVcMsb);
 
-        Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.primaryVersionCounter);
-        pvc.putLong(BluetoothPbapUtils.primaryVersionCounter);
+        Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.sPrimaryVersionCounter);
+        pvc.putLong(BluetoothPbapUtils.sPrimaryVersionCounter);
         return pvc.array();
     }
 
@@ -1349,14 +1455,14 @@
         ByteBuffer svc = ByteBuffer.allocate(16);
         svc.putLong(secondaryVcMsb);
 
-        Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.secondaryVersionCounter);
-        svc.putLong(BluetoothPbapUtils.secondaryVersionCounter);
+        Log.d(TAG, "secondaryVersionCounter is " + BluetoothPbapUtils.sSecondaryVersionCounter);
+        svc.putLong(BluetoothPbapUtils.sSecondaryVersionCounter);
         return svc.array();
     }
 
     private boolean checkPbapFeatureSupport(long featureBitMask) {
         Log.d(TAG, "checkPbapFeatureSupport featureBitMask is " + featureBitMask);
-        return ((ByteBuffer.wrap(connAppParamValue.supportedFeature).getInt() & featureBitMask)
+        return ((ByteBuffer.wrap(mConnAppParamValue.supportedFeature).getInt() & featureBitMask)
                 != 0);
     }
 }
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index 7e1164e..374b5a1 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -32,51 +32,39 @@
 
 package com.android.bluetooth.pbap;
 
-import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothPbap;
 import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothServerSocket;
 import android.bluetooth.BluetoothSocket;
-import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothPbap;
-import android.database.sqlite.SQLiteException;
 import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.database.sqlite.SQLiteException;
 import android.os.Handler;
-import android.os.IBinder;
+import android.os.HandlerThread;
+import android.os.Looper;
 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;
 
-import com.android.bluetooth.BluetoothObexTransport;
-import com.android.bluetooth.btservice.ProfileService;
-import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
 import com.android.bluetooth.IObexConnectionHandler;
 import com.android.bluetooth.ObexServerSockets;
 import com.android.bluetooth.R;
-import com.android.bluetooth.sdp.SdpManager;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.sdp.SdpManager;
 import com.android.bluetooth.util.DevicePolicyUtils;
 
-import java.io.IOException;
-import java.util.Calendar;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.ArrayList;
 import java.util.HashMap;
-
-import javax.obex.ServerSession;
+import java.util.List;
 
 public class BluetoothPbapService extends ProfileService implements IObexConnectionHandler {
     private static final String TAG = "BluetoothPbapService";
@@ -96,244 +84,179 @@
      * Intent indicating incoming obex authentication request which is from
      * PCE(Carkit)
      */
-    public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall";
+    static final String AUTH_CHALL_ACTION = "com.android.bluetooth.pbap.authchall";
 
     /**
      * Intent indicating obex session key input complete by user which is sent
      * from BluetoothPbapActivity
      */
-    public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse";
+    static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.pbap.authresponse";
 
     /**
      * Intent indicating user canceled obex authentication session key input
      * which is sent from BluetoothPbapActivity
      */
-    public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled";
+    static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.pbap.authcancelled";
 
     /**
      * Intent indicating timeout for user confirmation, which is sent to
      * BluetoothPbapActivity
      */
-    public static final String USER_CONFIRM_TIMEOUT_ACTION =
+    static final String USER_CONFIRM_TIMEOUT_ACTION =
             "com.android.bluetooth.pbap.userconfirmtimeout";
 
     /**
      * Intent Extra name indicating session key which is sent from
      * BluetoothPbapActivity
      */
-    public static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey";
+    static final String EXTRA_SESSION_KEY = "com.android.bluetooth.pbap.sessionkey";
+    static final String EXTRA_DEVICE = "com.android.bluetooth.pbap.device";
+    static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
 
-    public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
+    static final int MSG_ACQUIRE_WAKE_LOCK = 5004;
+    static final int MSG_RELEASE_WAKE_LOCK = 5005;
+    static final int MSG_STATE_MACHINE_DONE = 5006;
 
-    public static final int MSG_SERVERSESSION_CLOSE = 5000;
-
-    public static final int MSG_SESSION_ESTABLISHED = 5001;
-
-    public static final int MSG_SESSION_DISCONNECTED = 5002;
-
-    public static final int MSG_OBEX_AUTH_CHALL = 5003;
-
-    public static final int MSG_ACQUIRE_WAKE_LOCK = 5004;
-
-    public static final int MSG_RELEASE_WAKE_LOCK = 5005;
-
-    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
-
+    static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
 
-    private static final int START_LISTENER = 1;
+    static final int START_LISTENER = 1;
+    static final int USER_TIMEOUT = 2;
+    static final int SHUTDOWN = 3;
+    static final int LOAD_CONTACTS = 4;
+    static final int CONTACTS_LOADED = 5;
+    static final int CHECK_SECONDARY_VERSION_COUNTER = 6;
+    static final int ROLLOVER_COUNTERS = 7;
 
-    private static final int USER_TIMEOUT = 2;
+    static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
+    static final int RELEASE_WAKE_LOCK_DELAY = 10000;
 
-    private static final int AUTH_TIMEOUT = 3;
+    private PowerManager.WakeLock mWakeLock;
 
-    private static final int SHUTDOWN = 4;
-
-    protected static final int LOAD_CONTACTS = 5;
-
-    private static final int CHECK_SECONDARY_VERSION_COUNTER = 6;
-
-    protected static final int ROLLOVER_COUNTERS = 7;
-
-    private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
-
-    private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
-
-    // Ensure not conflict with Opp notification ID
-    private static final int NOTIFICATION_ID_ACCESS = -1000001;
-
-    private static final int NOTIFICATION_ID_AUTH = -1000002;
-
-    private static final String PBAP_NOTIFICATION_CHANNEL = "pbap_notification_channel";
-
-    private PowerManager.WakeLock mWakeLock = null;
-
-    private BluetoothPbapAuthenticator mAuth = null;
-
-    private BluetoothPbapObexServer mPbapServer;
-
-    private ServerSession mServerSession = null;
-
-    private BluetoothServerSocket mServerSocket = null;
-
-    private BluetoothSocket mConnSocket = null;
-
-    private BluetoothDevice mRemoteDevice = null;
-
-    private static String sLocalPhoneNum = null;
-
-    private static String sLocalPhoneName = null;
-
-    private static String sRemoteDeviceName = null;
-
-    private volatile boolean mInterrupted;
-
-    private int mState;
-
-    private boolean mIsWaitingAuthorization = false;
+    private static String sLocalPhoneNum;
+    private static String sLocalPhoneName;
 
     private ObexServerSockets mServerSockets = null;
 
     private static final int SDP_PBAP_SERVER_VERSION = 0x0102;
-
-    private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0003;
-
+    private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0001;
     private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F;
 
-    private AlarmManager mAlarmManager = null;
+    /* PBAP will use Bluetooth notification ID from 1000000 (included) to 2000000 (excluded).
+       The notification ID should be unique in Bluetooth package. */
+    private static final int PBAP_NOTIFICATION_ID_START = 1000000;
+    private static final int PBAP_NOTIFICATION_ID_END = 2000000;
 
     private int mSdpHandle = -1;
 
-    private boolean mRemoveTimeoutMsg = false;
-
-    private int mPermission = BluetoothDevice.ACCESS_UNKNOWN;
-
-    private boolean mSdpSearchInitiated = false;
-
-    private boolean isRegisteredObserver = false;
-
     protected Context mContext;
 
+    private PbapHandler mSessionStatusHandler;
+    private HandlerThread mHandlerThread;
+    private final HashMap<BluetoothDevice, PbapStateMachine> mPbapStateMachineMap = new HashMap<>();
+    private volatile int mNextNotificationId = PBAP_NOTIFICATION_ID_START;
+
     // package and class name to which we send intent to check phone book access permission
     private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
     private static final String ACCESS_AUTHORITY_CLASS =
             "com.android.settings.bluetooth.BluetoothPermissionRequest";
 
+    private Thread mThreadLoadContacts;
+    private boolean mContactsLoaded = false;
+
+    private Thread mThreadUpdateSecVersionCounter;
+
+    private static BluetoothPbapService sBluetoothPbapService;
+
     private class BluetoothPbapContentObserver extends ContentObserver {
-        public BluetoothPbapContentObserver() {
+        BluetoothPbapContentObserver() {
             super(new Handler());
         }
 
         @Override
         public void onChange(boolean selfChange) {
             Log.d(TAG, " onChange on contact uri ");
-            if (BluetoothPbapUtils.contactsLoaded) {
-                if (!mSessionStatusHandler.hasMessages(CHECK_SECONDARY_VERSION_COUNTER)) {
-                    mSessionStatusHandler.sendMessage(
-                            mSessionStatusHandler.obtainMessage(CHECK_SECONDARY_VERSION_COUNTER));
-                }
+            sendUpdateRequest();
+        }
+    }
+
+    private void sendUpdateRequest() {
+        if (mContactsLoaded) {
+            if (!mSessionStatusHandler.hasMessages(CHECK_SECONDARY_VERSION_COUNTER)) {
+                mSessionStatusHandler.sendMessage(
+                        mSessionStatusHandler.obtainMessage(CHECK_SECONDARY_VERSION_COUNTER));
             }
         }
     }
 
     private BluetoothPbapContentObserver mContactChangeObserver;
 
-    public BluetoothPbapService() {
-        mState = BluetoothPbap.STATE_DISCONNECTED;
-        mContext = this;
-    }
-
-    // process the intent from receiver
     private void parseIntent(final Intent intent) {
         String action = intent.getAction();
-        if (DEBUG) Log.d(TAG, "action: " + action);
-        if (action == null) return;             // Nothing to do
-        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
-        if (DEBUG) Log.d(TAG, "state: " + state);
-        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-            if (state == BluetoothAdapter.STATE_TURNING_OFF) {
-                // Send any pending timeout now, as this service will be destroyed.
-                if (mSessionStatusHandler.hasMessages(USER_TIMEOUT)) {
-                    mSessionStatusHandler.removeMessages(USER_TIMEOUT);
-                    mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
-                }
-                // Release all resources
-                closeService();
-            } else if (state == BluetoothAdapter.STATE_ON) {
-                // start RFCOMM listener
-                mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
-            }
-            return;
+        if (DEBUG) {
+            Log.d(TAG, "action: " + action);
         }
-
-        if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) {
-            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
-            if (mRemoteDevice == null) return;
-            if (DEBUG) Log.d(TAG,"ACL disconnected for "+ device);
-            if (mRemoteDevice.equals(device)) {
-                mSessionStatusHandler.removeMessages(USER_TIMEOUT);
-                mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
-            }
-            return;
-        }
-
-        if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
+        if (BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY.equals(action)) {
             int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-                                           BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
-
-            if ((!mIsWaitingAuthorization)
-                    || (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS)) {
-                // this reply is not for us
+                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
+            if (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
                 return;
             }
 
-            mSessionStatusHandler.removeMessages(USER_TIMEOUT);
-            mIsWaitingAuthorization = false;
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            synchronized (mPbapStateMachineMap) {
+                PbapStateMachine sm = mPbapStateMachineMap.get(device);
+                if (sm == null) {
+                    Log.w(TAG, "device not connected! device=" + device);
+                    return;
+                }
+                mSessionStatusHandler.removeMessages(USER_TIMEOUT, sm);
+                int access = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+                        BluetoothDevice.CONNECTION_ACCESS_NO);
+                boolean savePreference = intent.getBooleanExtra(
+                        BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false);
 
-            if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
-                                   BluetoothDevice.CONNECTION_ACCESS_NO)
-                    == BluetoothDevice.CONNECTION_ACCESS_YES) {
-                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
-                    boolean result = mRemoteDevice.setPhonebookAccessPermission(
-                            BluetoothDevice.ACCESS_ALLOWED);
-                    if (VERBOSE) {
-                        Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)=" + result);
+                if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
+                    if (savePreference) {
+                        device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+                        if (VERBOSE) {
+                            Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)");
+                        }
                     }
-                }
-                try {
-                    if (mConnSocket != null) {
-                        startObexServerSession();
-                    } else {
-                        stopObexServerSession();
+                    sm.sendMessage(PbapStateMachine.AUTHORIZED);
+                } else {
+                    if (savePreference) {
+                        device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+                        if (VERBOSE) {
+                            Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)");
+                        }
                     }
-                } catch (IOException ex) {
-                    Log.e(TAG, "Caught the error: " + ex.toString());
+                    sm.sendMessage(PbapStateMachine.REJECTED);
                 }
-            } else {
-                if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
-                    boolean result = mRemoteDevice.setPhonebookAccessPermission(
-                            BluetoothDevice.ACCESS_REJECTED);
-                    if (VERBOSE) {
-                        Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)=" + result);
-                    }
-                }
-                stopObexServerSession();
             }
-            return;
-        }
-
-        if (action.equals(AUTH_RESPONSE_ACTION)) {
-            String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
-            notifyAuthKeyInput(sessionkey);
-        } else if (action.equals(AUTH_CANCELLED_ACTION)) {
-            notifyAuthCancelled();
+        } else if (AUTH_RESPONSE_ACTION.equals(action)) {
+            String sessionKey = intent.getStringExtra(EXTRA_SESSION_KEY);
+            BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
+            synchronized (mPbapStateMachineMap) {
+                PbapStateMachine sm = mPbapStateMachineMap.get(device);
+                if (sm == null) {
+                    return;
+                }
+                Message msg = sm.obtainMessage(PbapStateMachine.AUTH_KEY_INPUT, sessionKey);
+                sm.sendMessage(msg);
+            }
+        } else if (AUTH_CANCELLED_ACTION.equals(action)) {
+            BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
+            synchronized (mPbapStateMachineMap) {
+                PbapStateMachine sm = mPbapStateMachineMap.get(device);
+                if (sm == null) {
+                    return;
+                }
+                sm.sendMessage(PbapStateMachine.AUTH_CANCELLED);
+            }
         } else {
-            Log.w(TAG, "Unrecognized intent!");
-            return;
+            Log.w(TAG, "Unhandled intent action: " + action);
         }
-
-        mSessionStatusHandler.removeMessages(USER_TIMEOUT);
     }
 
     private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() {
@@ -343,494 +266,215 @@
         }
     };
 
-    private final boolean initSocket() {
-        if (VERBOSE) Log.v(TAG, "Pbap Service initSocket");
-
-        boolean initSocketOK = false;
-        final int CREATE_RETRY_TIME = 10;
-
-        // It's possible that create will fail in some cases. retry for 10 times
-        for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
-            initSocketOK = true;
-            try {
-                // It is mandatory for PSE to support initiation of bonding and
-                // encryption.
-                mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord
-                    ("OBEX Phonebook Access Server", BluetoothUuid.PBAP_PSE.getUuid());
-
-            } catch (IOException e) {
-                Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
-                initSocketOK = false;
-            }
-            if (!initSocketOK) {
-                // Need to break out of this loop if BT is being turned off.
-                if (mAdapter == null) break;
-                int state = mAdapter.getState();
-                if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
-                    (state != BluetoothAdapter.STATE_ON)) {
-                    Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
-                    break;
-                }
-                try {
-                    if (VERBOSE) Log.v(TAG, "wait 300 ms");
-                    Thread.sleep(300);
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
-                    break;
-                }
-            } else {
-                break;
-            }
+    private void closeService() {
+        if (VERBOSE) {
+            Log.v(TAG, "Pbap Service closeService");
         }
 
-        if (mInterrupted) {
-            initSocketOK = false;
-            // close server socket to avoid resource leakage
-            closeServerSocket();
-        }
+        BluetoothPbapUtils.savePbapParams(this);
 
-        if (initSocketOK) {
-            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
-
-        } else {
-            Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
-        }
-        return initSocketOK;
-    }
-
-    private final synchronized void closeServerSocket() {
-        // exit SocketAcceptThread early
-        if (mServerSocket != null) {
-            try {
-                // this will cause mServerSocket.accept() return early with IOException
-                mServerSocket.close();
-                mServerSocket = null;
-            } catch (IOException ex) {
-                Log.e(TAG, "Close Server Socket error: " + ex);
-            }
-        }
-    }
-
-    private final synchronized void closeConnectionSocket() {
-        if (mConnSocket != null) {
-            try {
-                mConnSocket.close();
-                mConnSocket = null;
-            } catch (IOException e) {
-                Log.e(TAG, "Close Connection Socket error: " + e.toString());
-            }
-        }
-    }
-
-    private final void closeService() {
-        if (VERBOSE) Log.v(TAG, "Pbap Service closeService in");
-
-        BluetoothPbapUtils.savePbapParams(this, BluetoothPbapUtils.primaryVersionCounter,
-                BluetoothPbapUtils.secondaryVersionCounter, BluetoothPbapUtils.mDbIdentifier.get(),
-                BluetoothPbapUtils.contactsLastUpdated, BluetoothPbapUtils.totalFields,
-                BluetoothPbapUtils.totalSvcFields, BluetoothPbapUtils.totalContacts);
-
-        // exit initSocket early
-        mInterrupted = true;
         if (mWakeLock != null) {
             mWakeLock.release();
             mWakeLock = null;
         }
 
-        // Step 1: clean up active server session
-        if (mServerSession != null) {
-            mServerSession.close();
-            mServerSession = null;
+        cleanUpServerSocket();
+
+        if (mSessionStatusHandler != null) {
+            mSessionStatusHandler.removeCallbacksAndMessages(null);
         }
-        // Step 2: clean up existing connection socket
-        closeConnectionSocket();
+    }
+
+    private void cleanUpServerSocket() {
+        // Step 1, 2: clean up active server session and connection socket
+        synchronized (mPbapStateMachineMap) {
+            for (PbapStateMachine stateMachine : mPbapStateMachineMap.values()) {
+                stateMachine.sendMessage(PbapStateMachine.DISCONNECT);
+            }
+        }
         // Step 3: clean up SDP record
         cleanUpSdpRecord();
-        // Step 4: clean up existing server socket(s)
-        closeServerSocket();
+        // Step 4: clean up existing server sockets
         if (mServerSockets != null) {
             mServerSockets.shutdown(false);
             mServerSockets = null;
         }
-        if (mSessionStatusHandler != null) mSessionStatusHandler.removeCallbacksAndMessages(null);
-        if (VERBOSE) Log.v(TAG, "Pbap Service closeService out");
+    }
+
+    private void createSdpRecord() {
+        if (mSdpHandle > -1) {
+            Log.w(TAG, "createSdpRecord, SDP record already created");
+        }
+        mSdpHandle = SdpManager.getDefaultManager()
+                .createPbapPseRecord("OBEX Phonebook Access Server",
+                        mServerSockets.getRfcommChannel(), mServerSockets.getL2capPsm(),
+                        SDP_PBAP_SERVER_VERSION, SDP_PBAP_SUPPORTED_REPOSITORIES,
+                        SDP_PBAP_SUPPORTED_FEATURES);
+        if (DEBUG) {
+            Log.d(TAG, "created Sdp record, mSdpHandle=" + mSdpHandle);
+        }
     }
 
     private void cleanUpSdpRecord() {
         if (mSdpHandle < 0) {
-            if (VERBOSE) Log.v(TAG, "cleanUpSdpRecord, SDP record never created");
+            Log.w(TAG, "cleanUpSdpRecord, SDP record never created");
             return;
         }
         int sdpHandle = mSdpHandle;
         mSdpHandle = -1;
         SdpManager sdpManager = SdpManager.getDefaultManager();
+        if (DEBUG) {
+            Log.d(TAG, "cleanUpSdpRecord, mSdpHandle=" + sdpHandle);
+        }
         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);
+            Log.e(TAG, "sdpManager is null");
+        } else if (!sdpManager.removeSdpRecord(sdpHandle)) {
+            Log.w(TAG, "cleanUpSdpRecord, removeSdpRecord failed, sdpHandle=" + sdpHandle);
         }
     }
 
-    private final void startObexServerSession() throws IOException {
-        if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
-
-        // acquire the wakeLock before start Obex transaction thread
-        if (mWakeLock == null) {
-            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
-            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                    "StartingObexPbapTransaction");
-            mWakeLock.setReferenceCounted(false);
-            mWakeLock.acquire();
-        }
-        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);
-            }
+    private class PbapHandler extends Handler {
+        private PbapHandler(Looper looper) {
+            super(looper);
         }
 
-        mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this);
-        synchronized (this) {
-            mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
-            mAuth.setChallenged(false);
-            mAuth.setCancelled(false);
-        }
-        BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
-        mServerSession = new ServerSession(transport, mPbapServer, mAuth);
-        setState(BluetoothPbap.STATE_CONNECTED);
-
-        mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
-        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
-            .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
-
-        if (VERBOSE) {
-            Log.v(TAG, "startObexServerSession() success!");
-        }
-    }
-
-    private void stopObexServerSession() {
-        if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
-        mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
-        mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
-        // Release the wake lock if obex transaction is over
-        if (mWakeLock != null) {
-            mWakeLock.release();
-            mWakeLock = null;
-        }
-
-        if (mServerSession != null) {
-            mServerSession.close();
-            mServerSession = null;
-        }
-        closeConnectionSocket();
-
-        // Last obex transaction is finished, we start to listen for incoming
-        // connection again
-        if (mAdapter != null && mAdapter.isEnabled()) {
-            startSocketListeners();
-        }
-        setState(BluetoothPbap.STATE_DISCONNECTED);
-    }
-
-    private void notifyAuthKeyInput(final String key) {
-        synchronized (mAuth) {
-            if (key != null) {
-                mAuth.setSessionKey(key);
-            }
-            mAuth.setChallenged(true);
-            mAuth.notify();
-        }
-    }
-
-    private void notifyAuthCancelled() {
-        synchronized (mAuth) {
-            mAuth.setCancelled(true);
-            mAuth.notify();
-        }
-    }
-
-    /**
-     * A thread that runs in the background waiting for remote rfcomm
-     * connect.Once a remote socket connected, this thread shall be
-     * shutdown.When the remote disconnect,this thread shall run again waiting
-     * for next request.
-     */
-    private class SocketAcceptThread extends Thread {
-
-        private boolean stopped = false;
-
-        @Override
-        public void run() {
-            BluetoothServerSocket serverSocket;
-            if (mServerSocket == null) {
-                if (!initSocket()) {
-                    return;
-                }
-            }
-
-            while (!stopped) {
-                try {
-                    if (VERBOSE) Log.v(TAG, "Accepting socket connection...");
-                    serverSocket = mServerSocket;
-                    if (serverSocket == null) {
-                        Log.w(TAG, "mServerSocket is null");
-                        break;
-                    }
-                    mConnSocket = serverSocket.accept();
-                    if (VERBOSE) Log.v(TAG, "Accepted socket connection...");
-
-                    synchronized (BluetoothPbapService.this) {
-                        if (mConnSocket == null) {
-                            Log.w(TAG, "mConnSocket is null");
-                            break;
-                        }
-                        mRemoteDevice = mConnSocket.getRemoteDevice();
-                    }
-                    if (mRemoteDevice == null) {
-                        Log.i(TAG, "getRemoteDevice() = null");
-                        break;
-                    }
-                    sRemoteDeviceName = mRemoteDevice.getName();
-                    // In case getRemoteName failed and return null
-                    if (TextUtils.isEmpty(sRemoteDeviceName)) {
-                        sRemoteDeviceName = getString(R.string.defaultname);
-                    }
-                    int permission = mRemoteDevice.getPhonebookAccessPermission();
-                    if (VERBOSE) Log.v(TAG, "getPhonebookAccessPermission() = " + permission);
-
-                    if (permission == BluetoothDevice.ACCESS_ALLOWED) {
-                        try {
-                            if (VERBOSE) {
-                                Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName
-                                        + " automatically as already allowed device");
-                            }
-                            startObexServerSession();
-                        } catch (IOException ex) {
-                            Log.e(TAG, "Caught exception starting obex server session"
-                                    + ex.toString());
-                        }
-                    } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
-                        if (VERBOSE) {
-                            Log.v(TAG, "incoming connection rejected from: " + sRemoteDeviceName
-                                    + " automatically as already rejected device");
-                        }
-                        stopObexServerSession();
-                    } else {  // permission == BluetoothDevice.ACCESS_UNKNOWN
-                        // Send an Intent to Settings app to ask user preference.
-                        Intent intent =
-                                new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
-                        intent.setPackage(getString(R.string.pairing_ui_package));
-                        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-                                        BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
-                        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
-                        intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
-                        intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, getName());
-
-                        mIsWaitingAuthorization = true;
-                        sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-
-                        if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
-                                + sRemoteDeviceName);
-
-                        // In case car kit time out and try to use HFP for
-                        // phonebook
-                        // access, while UI still there waiting for user to
-                        // confirm
-                        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
-                                .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
-                        // We will continue the process when we receive
-                        // BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app.
-                    }
-                    stopped = true; // job done ,close this thread;
-                } catch (IOException ex) {
-                    stopped=true;
-                    /*
-                    if (stopped) {
-                        break;
-                    }
-                    */
-                    if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
-                }
-            }
-        }
-
-        void shutdown() {
-            stopped = true;
-            interrupt();
-        }
-    }
-
-    protected final Handler mSessionStatusHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
-            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
+            if (VERBOSE) {
+                Log.v(TAG, "Handler(): got msg=" + msg.what);
+            }
 
             switch (msg.what) {
                 case START_LISTENER:
-                    if (mAdapter.isEnabled()) {
-                        startSocketListeners();
+                    mServerSockets = ObexServerSockets.create(BluetoothPbapService.this);
+                    if (mServerSockets == null) {
+                        Log.w(TAG, "ObexServerSockets.create() returned null");
+                        break;
                     }
+                    createSdpRecord();
+                    // fetch Pbap Params to check if significant change has happened to Database
+                    BluetoothPbapUtils.fetchPbapParams(mContext);
                     break;
                 case USER_TIMEOUT:
                     Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
                     intent.setPackage(getString(R.string.pairing_ui_package));
-                    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+                    PbapStateMachine stateMachine = (PbapStateMachine) msg.obj;
+                    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, stateMachine.getRemoteDevice());
                     intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-                                    BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
+                            BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
                     sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-                    mIsWaitingAuthorization = false;
-                    stopObexServerSession();
-                    break;
-                case AUTH_TIMEOUT:
-                    Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
-                    sendBroadcast(i);
-                    removePbapNotification(NOTIFICATION_ID_AUTH);
-                    notifyAuthCancelled();
-                    break;
-                case MSG_SERVERSESSION_CLOSE:
-                    stopObexServerSession();
-                    break;
-                case MSG_SESSION_ESTABLISHED:
-                    break;
-                case MSG_SESSION_DISCONNECTED:
-                    // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
-                    break;
-                case MSG_OBEX_AUTH_CHALL:
-                    createPbapNotification(AUTH_CHALL_ACTION);
-                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
-                            .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
+                    stateMachine.sendMessage(PbapStateMachine.REJECTED);
                     break;
                 case MSG_ACQUIRE_WAKE_LOCK:
                     if (mWakeLock == null) {
-                        PowerManager pm = (PowerManager)getSystemService(
-                                          Context.POWER_SERVICE);
+                        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
                         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                                    "StartingObexPbapTransaction");
+                                "StartingObexPbapTransaction");
                         mWakeLock.setReferenceCounted(false);
                         mWakeLock.acquire();
                         Log.w(TAG, "Acquire Wake Lock");
                     }
                     mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
-                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
-                      .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
+                    mSessionStatusHandler.sendMessageDelayed(
+                            mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
+                            RELEASE_WAKE_LOCK_DELAY);
                     break;
                 case MSG_RELEASE_WAKE_LOCK:
                     if (mWakeLock != null) {
                         mWakeLock.release();
                         mWakeLock = null;
-                        Log.w(TAG, "Release Wake Lock");
                     }
                     break;
                 case SHUTDOWN:
                     closeService();
                     break;
                 case LOAD_CONTACTS:
-                    BluetoothPbapUtils.loadAllContacts(mContext, this);
+                    loadAllContacts();
+                    break;
+                case CONTACTS_LOADED:
+                    mContactsLoaded = true;
                     break;
                 case CHECK_SECONDARY_VERSION_COUNTER:
-                    BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, this);
+                    updateSecondaryVersion();
                     break;
                 case ROLLOVER_COUNTERS:
                     BluetoothPbapUtils.rolloverCounters();
                     break;
+                case MSG_STATE_MACHINE_DONE:
+                    PbapStateMachine sm = (PbapStateMachine) msg.obj;
+                    BluetoothDevice remoteDevice = sm.getRemoteDevice();
+                    sm.quitNow();
+                    synchronized (mPbapStateMachineMap) {
+                        mPbapStateMachineMap.remove(remoteDevice);
+                    }
+                    break;
                 default:
                     break;
             }
         }
-    };
-
-    private void setState(int state) {
-        setState(state, BluetoothPbap.RESULT_SUCCESS);
     }
 
-    private synchronized void setState(int state, int result) {
-        if (state != mState) {
-            if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
-                    + result);
-            int prevState = mState;
-            mState = state;
-            Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
-            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
-            intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
-            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
-            sendBroadcast(intent, BLUETOOTH_PERM);
+    int getConnectionState(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (mPbapStateMachineMap == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+
+        synchronized (mPbapStateMachineMap) {
+            PbapStateMachine sm = mPbapStateMachineMap.get(device);
+            if (sm == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+            return sm.getConnectionState();
         }
     }
 
-    protected int getState() {
-        return mState;
-    }
-
-    protected BluetoothDevice getRemoteDevice() {
-        return mRemoteDevice;
-    }
-
-    private void createPbapNotification(String action) {
-
-        NotificationManager nm = (NotificationManager)
-            getSystemService(Context.NOTIFICATION_SERVICE);
-        NotificationChannel notificationChannel = new NotificationChannel(PBAP_NOTIFICATION_CHANNEL,
-                getString(R.string.pbap_notification_group), NotificationManager.IMPORTANCE_HIGH);
-        nm.createNotificationChannel(notificationChannel);
-
-        // Create an intent triggered by clicking on the status icon.
-        Intent clickIntent = new Intent();
-        clickIntent.setClass(this, BluetoothPbapActivity.class);
-        clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        clickIntent.setAction(action);
-
-        // Create an intent triggered by clicking on the
-        // "Clear All Notifications" button
-        Intent deleteIntent = new Intent();
-        deleteIntent.setClass(this, BluetoothPbapService.class);
-        deleteIntent.setAction(AUTH_CANCELLED_ACTION);
-
-        String name = getRemoteDeviceName();
-
-        if (action.equals(AUTH_CHALL_ACTION)) {
-            Notification notification =
-                    new Notification.Builder(this, PBAP_NOTIFICATION_CHANNEL)
-                            .setWhen(System.currentTimeMillis())
-                            .setContentTitle(getString(R.string.auth_notif_title))
-                            .setContentText(getString(R.string.auth_notif_message, name))
-                            .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
-                            .setTicker(getString(R.string.auth_notif_ticker))
-                            .setColor(getResources().getColor(
-                                    com.android.internal.R.color.system_notification_accent_color,
-                                    this.getTheme()))
-                            .setFlag(Notification.FLAG_AUTO_CANCEL, true)
-                            .setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
-                            .setDefaults(Notification.DEFAULT_SOUND)
-                            .setContentIntent(PendingIntent.getActivity(this, 0, clickIntent, 0))
-                            .setDeleteIntent(PendingIntent.getBroadcast(this, 0, deleteIntent, 0))
-                            .build();
-            nm.notify(NOTIFICATION_ID_AUTH, notification);
+    List<BluetoothDevice> getConnectedDevices() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        if (mPbapStateMachineMap == null) {
+            return new ArrayList<>();
+        }
+        synchronized (mPbapStateMachineMap) {
+            return new ArrayList<>(mPbapStateMachineMap.keySet());
         }
     }
 
-    private void removePbapNotification(int id) {
-        NotificationManager nm = (NotificationManager)
-            getSystemService(Context.NOTIFICATION_SERVICE);
-        nm.cancel(id);
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        List<BluetoothDevice> devices = new ArrayList<>();
+        if (mPbapStateMachineMap == null || states == null) {
+            return devices;
+        }
+        synchronized (mPbapStateMachineMap) {
+            for (int state : states) {
+                for (BluetoothDevice device : mPbapStateMachineMap.keySet()) {
+                    if (state == mPbapStateMachineMap.get(device).getConnectionState()) {
+                        devices.add(device);
+                    }
+                }
+            }
+        }
+        return devices;
     }
 
-    public static String getLocalPhoneNum() {
+    void disconnect(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        synchronized (mPbapStateMachineMap) {
+            PbapStateMachine sm = mPbapStateMachineMap.get(device);
+            if (sm != null) {
+                sm.sendMessage(PbapStateMachine.DISCONNECT);
+            }
+        }
+    }
+
+    static String getLocalPhoneNum() {
         return sLocalPhoneNum;
     }
 
-    public static String getLocalPhoneName() {
+    static String getLocalPhoneName() {
         return sLocalPhoneName;
     }
 
-    public static String getRemoteDeviceName() {
-        return sRemoteDeviceName;
-    }
-
     @Override
     protected IProfileServiceBinder initBinder() {
         return new PbapBinder(this);
@@ -838,253 +482,297 @@
 
     @Override
     protected boolean start() {
-        Log.v(TAG, "start()");
+        if (VERBOSE) {
+            Log.v(TAG, "start()");
+        }
+        mContext = this;
+        mContactsLoaded = false;
+        mHandlerThread = new HandlerThread("PbapHandlerThread");
+        mHandlerThread.start();
+        mSessionStatusHandler = new PbapHandler(mHandlerThread.getLooper());
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
-        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         filter.addAction(AUTH_RESPONSE_ACTION);
         filter.addAction(AUTH_CANCELLED_ACTION);
-        mInterrupted = false;
         BluetoothPbapConfig.init(this);
-        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
-        if (mContactChangeObserver == null) {
-            registerReceiver(mPbapReceiver, filter);
-            try {
-                if (DEBUG) Log.d(TAG, "Registering observer");
-                mContactChangeObserver = new BluetoothPbapContentObserver();
-                getContentResolver().registerContentObserver(
-                        DevicePolicyUtils.getEnterprisePhoneUri(this), false,
-                        mContactChangeObserver);
-            } catch (SQLiteException e) {
-                Log.e(TAG, "SQLite exception: " + e);
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "Illegal state exception, content observer is already registered");
+        registerReceiver(mPbapReceiver, filter);
+        try {
+            mContactChangeObserver = new BluetoothPbapContentObserver();
+            getContentResolver().registerContentObserver(
+                    DevicePolicyUtils.getEnterprisePhoneUri(this), false,
+                    mContactChangeObserver);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "SQLite exception: " + e);
+        } catch (IllegalStateException e) {
+            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);
             }
         }
+
+        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
+        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
+        setBluetoothPbapService(this);
         return true;
     }
 
     @Override
     protected boolean stop() {
-        Log.v(TAG, "stop()");
+        if (VERBOSE) {
+            Log.v(TAG, "stop()");
+        }
+        setBluetoothPbapService(null);
+        if (mSessionStatusHandler != null) {
+            mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
+        }
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+        }
+        mContactsLoaded = false;
         if (mContactChangeObserver == null) {
             Log.i(TAG, "Avoid unregister when receiver it is not registered");
             return true;
         }
-        try {
-            unregisterReceiver(mPbapReceiver);
-            getContentResolver().unregisterContentObserver(mContactChangeObserver);
-            mContactChangeObserver = null;
-        } catch (Exception e) {
-            Log.w(TAG, "Unable to unregister pbap receiver", e);
-        }
-        mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
-        setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
+        unregisterReceiver(mPbapReceiver);
+        getContentResolver().unregisterContentObserver(mContactChangeObserver);
+        mContactChangeObserver = null;
         return true;
     }
 
-    protected void disconnect() {
-        synchronized (this) {
-            if (mState == BluetoothPbap.STATE_CONNECTED) {
-                if (mServerSession != null) {
-                    mServerSession.close();
-                    mServerSession = null;
-                }
+    /**
+     * Get the current instance of {@link BluetoothPbapService}
+     *
+     * @return current instance of {@link BluetoothPbapService}
+     */
+    @VisibleForTesting
+    public static synchronized BluetoothPbapService getBluetoothPbapService() {
+        if (sBluetoothPbapService == null) {
+            Log.w(TAG, "getBluetoothPbapService(): service is null");
+            return null;
+        }
+        if (!sBluetoothPbapService.isAvailable()) {
+            Log.w(TAG, "getBluetoothPbapService(): service is not available");
+            return null;
+        }
+        return sBluetoothPbapService;
+    }
 
-                closeConnectionSocket();
+    private static synchronized void setBluetoothPbapService(BluetoothPbapService instance) {
+        if (DEBUG) {
+            Log.d(TAG, "setBluetoothPbapService(): set to: " + instance);
+        }
+        sBluetoothPbapService = instance;
+    }
 
-                setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
-            }
+    @Override
+    protected void setCurrentUser(int userId) {
+        Log.i(TAG, "setCurrentUser(" + userId + ")");
+        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+        if (userManager.isUserUnlocked(userId)) {
+            setUserUnlocked(userId);
         }
     }
 
-    // Has to be a static class or a memory leak can occur.
+    @Override
+    protected void setUserUnlocked(int userId) {
+        Log.i(TAG, "setUserUnlocked(" + userId + ")");
+        sendUpdateRequest();
+    }
+
     private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder {
         private BluetoothPbapService mService;
 
-        private BluetoothPbapService getService(String perm) {
+        private BluetoothPbapService getService() {
             if (!Utils.checkCaller()) {
                 Log.w(TAG, "not allowed for non-active user");
                 return null;
             }
             if (mService != null && mService.isAvailable()) {
-                mService.enforceCallingOrSelfPermission(perm, "Need " + perm + " permission");
                 return mService;
             }
             return null;
         }
 
         PbapBinder(BluetoothPbapService service) {
-            Log.v(TAG, "PbapBinder()");
+            if (VERBOSE) {
+                Log.v(TAG, "PbapBinder()");
+            }
             mService = service;
         }
 
-        public boolean cleanup() {
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
-        public int getState() {
-            if (DEBUG) Log.d(TAG, "getState = " + mService.getState());
-            BluetoothPbapService service = getService(BLUETOOTH_PERM);
-            if (service == null) return BluetoothPbap.STATE_DISCONNECTED;
-
-            return service.getState();
+        @Override
+        public List<BluetoothDevice> getConnectedDevices() {
+            if (DEBUG) {
+                Log.d(TAG, "getConnectedDevices");
+            }
+            BluetoothPbapService service = getService();
+            if (service == null) {
+                return new ArrayList<>(0);
+            }
+            return service.getConnectedDevices();
         }
 
-        public BluetoothDevice getClient() {
-            if (DEBUG) Log.d(TAG, "getClient = " + mService.getRemoteDevice());
-            BluetoothPbapService service = getService(BLUETOOTH_PERM);
-            if (service == null) return null;
-            return service.getRemoteDevice();
+        @Override
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+            if (DEBUG) {
+                Log.d(TAG, "getDevicesMatchingConnectionStates");
+            }
+            BluetoothPbapService service = getService();
+            if (service == null) {
+                return new ArrayList<>(0);
+            }
+            return service.getDevicesMatchingConnectionStates(states);
         }
 
-        public boolean isConnected(BluetoothDevice device) {
-            if (DEBUG) Log.d(TAG, "isConnected " + device);
-            BluetoothPbapService service = getService(BLUETOOTH_PERM);
-            if (service == null) return false;
-            return service.getState() == BluetoothPbap.STATE_CONNECTED
-                    && service.getRemoteDevice().equals(device);
+        @Override
+        public int getConnectionState(BluetoothDevice device) {
+            if (DEBUG) {
+                Log.d(TAG, "getConnectionState: " + device);
+            }
+            BluetoothPbapService service = getService();
+            if (service == null) {
+                return BluetoothAdapter.STATE_DISCONNECTED;
+            }
+            return service.getConnectionState(device);
         }
 
-        public boolean connect(BluetoothDevice device) {
-            BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
-            return false;
-        }
-
-        public void disconnect() {
-            if (DEBUG) Log.d(TAG, "disconnect");
-            BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
-            if (service == null) return;
-            service.disconnect();
-        }
-    }
-
-    /**
-     * Start server side socket listeners. Caller should make sure that adapter is in a ready state
-     * and SDP record is cleaned up. Otherwise, this method will fail.
-     */
-    synchronized private void startSocketListeners() {
-        if (DEBUG) Log.d(TAG, "startsocketListener");
-        if (mServerSession != null) {
-            if (DEBUG) Log.d(TAG, "mServerSession exists - shutting it down...");
-            mServerSession.close();
-            mServerSession = null;
-        }
-        closeConnectionSocket();
-        if (mServerSockets != null) {
-            mServerSockets.prepareForNewConnect();
-        } else {
-            mServerSockets = ObexServerSockets.create(this);
-            if (mServerSockets == null) {
-                // TODO: Handle - was not handled before
-                Log.e(TAG, "Failed to start the listeners");
+        @Override
+        public void disconnect(BluetoothDevice device) {
+            if (DEBUG) {
+                Log.d(TAG, "disconnect");
+            }
+            BluetoothPbapService service = getService();
+            if (service == null) {
                 return;
             }
-            if (mSdpHandle >= 0) {
-                Log.e(TAG, "SDP handle was not cleaned up, mSdpHandle=" + mSdpHandle);
-                return;
-            }
-            mSdpHandle = SdpManager.getDefaultManager().createPbapPseRecord(
-                    "OBEX Phonebook Access Server", mServerSockets.getRfcommChannel(),
-                    mServerSockets.getL2capPsm(), SDP_PBAP_SERVER_VERSION,
-                    SDP_PBAP_SUPPORTED_REPOSITORIES, SDP_PBAP_SUPPORTED_FEATURES);
-            // fetch Pbap Params to check if significant change has happened to Database
-            BluetoothPbapUtils.fetchPbapParams(mContext);
-
-            if (DEBUG) Log.d(TAG, "PBAP server with handle:" + mSdpHandle);
+            service.disconnect(device);
         }
     }
 
-    long getDbIdentifier() {
-        return BluetoothPbapUtils.mDbIdentifier.get();
-    }
-
-    private void setUserTimeoutAlarm() {
-        if (DEBUG) Log.d(TAG, "SetUserTimeOutAlarm()");
-        if (mAlarmManager == null) {
-            mAlarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
-        }
-        mRemoveTimeoutMsg = true;
-        Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
-        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
-        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
-                System.currentTimeMillis() + USER_CONFIRM_TIMEOUT_VALUE, pIntent);
-    }
-
     @Override
     public boolean onConnect(BluetoothDevice remoteDevice, BluetoothSocket socket) {
-        mRemoteDevice = remoteDevice;
-        if (mRemoteDevice == null || socket == null) {
-            Log.i(TAG, "mRemoteDevice :" + mRemoteDevice + " socket :" + socket);
+        if (remoteDevice == null || socket == null) {
+            Log.e(TAG, "onConnect(): Unexpected null. remoteDevice=" + remoteDevice
+                    + " socket=" + socket);
             return false;
         }
-        mConnSocket = socket;
-        sRemoteDeviceName = mRemoteDevice.getName();
-        // In case getRemoteName failed and return null
-        if (TextUtils.isEmpty(sRemoteDeviceName)) {
-            sRemoteDeviceName = getString(R.string.defaultname);
+
+        PbapStateMachine sm = PbapStateMachine.make(this, mHandlerThread.getLooper(), remoteDevice,
+                socket,  this, mSessionStatusHandler, mNextNotificationId);
+        mNextNotificationId++;
+        if (mNextNotificationId == PBAP_NOTIFICATION_ID_END) {
+            mNextNotificationId = PBAP_NOTIFICATION_ID_START;
         }
-        int permission = mRemoteDevice.getPhonebookAccessPermission();
-        if (DEBUG) Log.d(TAG, "getPhonebookAccessPermission() = " + permission);
+        synchronized (mPbapStateMachineMap) {
+            mPbapStateMachineMap.put(remoteDevice, sm);
+        }
+        sm.sendMessage(PbapStateMachine.REQUEST_PERMISSION);
+        return true;
+    }
+
+    /**
+     * Get the phonebook access permission for the device; if unknown, ask the user.
+     * Send the result to the state machine.
+     * @param stateMachine PbapStateMachine which sends the request
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public void checkOrGetPhonebookPermission(PbapStateMachine stateMachine) {
+        BluetoothDevice device = stateMachine.getRemoteDevice();
+        int permission = device.getPhonebookAccessPermission();
+        if (DEBUG) {
+            Log.d(TAG, "getPhonebookAccessPermission() = " + permission);
+        }
 
         if (permission == BluetoothDevice.ACCESS_ALLOWED) {
-            try {
-                startObexServerSession();
-            } catch (IOException ex) {
-                Log.e(TAG, "Caught exception starting obex server session" + ex.toString());
-            }
-
-            if (!BluetoothPbapUtils.contactsLoaded) {
-                mSessionStatusHandler.sendMessage(
-                        mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
-            }
-
+            stateMachine.sendMessage(PbapStateMachine.AUTHORIZED);
         } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
-            if (DEBUG) {
-                Log.d(TAG, "incoming connection rejected from: " + sRemoteDeviceName
-                                + " automatically as already rejected device");
-            }
-            return false;
+            stateMachine.sendMessage(PbapStateMachine.REJECTED);
         } else { // permission == BluetoothDevice.ACCESS_UNKNOWN
-            // Send an Intent to Settings app to ask user preference.
             Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
-            intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
+            intent.setClassName(BluetoothPbapService.ACCESS_AUTHORITY_PACKAGE,
+                    BluetoothPbapService.ACCESS_AUTHORITY_CLASS);
             intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
                     BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
-            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
-            intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
-            mIsWaitingAuthorization = true;
-            sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-            if (VERBOSE)
-                Log.v(TAG, "waiting for authorization for connection from: " + sRemoteDeviceName);
+            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+            intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, this.getPackageName());
+            this.sendOrderedBroadcast(intent, BluetoothPbapService.BLUETOOTH_ADMIN_PERM);
+            if (VERBOSE) {
+                Log.v(TAG, "waiting for authorization for connection from: " + device);
+            }
             /* In case car kit time out and try to use HFP for phonebook
              * access, while UI still there waiting for user to confirm */
-            mSessionStatusHandler.sendMessageDelayed(
-                    mSessionStatusHandler.obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
+            Message msg = mSessionStatusHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT,
+                    stateMachine);
+            mSessionStatusHandler.sendMessageDelayed(msg, USER_CONFIRM_TIMEOUT_VALUE);
             /* We will continue the process when we receive
              * BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app. */
         }
-        return true;
-    };
+    }
 
     /**
      * Called when an unrecoverable error occurred in an accept thread.
      * Close down the server socket, and restart.
-     * TODO: Change to message, to call start in correct context.
      */
     @Override
     public synchronized void onAcceptFailed() {
-        // Clean up SDP record first
-        cleanUpSdpRecord();
-        // Force socket listener to restart
-        if (mServerSockets != null) {
-            mServerSockets.shutdown(false);
-            mServerSockets = null;
+        Log.w(TAG, "PBAP server socket accept thread failed. Restarting the server socket");
+
+        if (mWakeLock != null) {
+            mWakeLock.release();
+            mWakeLock = null;
         }
-        if (!mInterrupted && mAdapter != null && mAdapter.isEnabled()) {
-            startSocketListeners();
+
+        cleanUpServerSocket();
+
+        if (mSessionStatusHandler != null) {
+            mSessionStatusHandler.removeCallbacksAndMessages(null);
+        }
+
+        synchronized (mPbapStateMachineMap) {
+            mPbapStateMachineMap.clear();
+        }
+
+        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
+    }
+
+    private void loadAllContacts() {
+        if (mThreadLoadContacts == null) {
+            Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                    BluetoothPbapUtils.loadAllContacts(mContext,
+                            mSessionStatusHandler);
+                    mThreadLoadContacts = null;
+                }
+            };
+            mThreadLoadContacts = new Thread(r);
+            mThreadLoadContacts.start();
+        }
+    }
+
+    private void updateSecondaryVersion() {
+        if (mThreadUpdateSecVersionCounter == null) {
+            Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                    BluetoothPbapUtils.updateSecondaryVersionCounter(mContext,
+                            mSessionStatusHandler);
+                    mThreadUpdateSecVersionCounter = null;
+                }
+            };
+            mThreadUpdateSecVersionCounter = new Thread(r);
+            mThreadUpdateSecVersionCounter.start();
         }
     }
 }
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
index 45f2e21..f8b8e5f 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
@@ -18,137 +18,88 @@
 package com.android.bluetooth.pbap;
 
 import android.content.Context;
-import android.content.ContentResolver;
-import android.content.res.AssetFileDescriptor;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Handler;
 import android.preference.PreferenceManager;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.Profile;
 import android.provider.ContactsContract.RawContactsEntity;
-
 import android.util.Log;
 
 import com.android.vcard.VCardComposer;
 import com.android.vcard.VCardConfig;
-import com.android.bluetooth.Utils;
-import com.android.bluetooth.pbap.BluetoothPbapService;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.lang.Math;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.Calendar;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
 
-public class BluetoothPbapUtils {
+class BluetoothPbapUtils {
     private static final String TAG = "BluetoothPbapUtils";
     private static final boolean V = BluetoothPbapService.VERBOSE;
 
-    public static int FILTER_PHOTO = 3;
-    public static int FILTER_TEL = 7;
-    public static int FILTER_NICKNAME = 23;
+    private static final int FILTER_PHOTO = 3;
+
     private static final long QUERY_CONTACT_RETRY_INTERVAL = 4000;
 
-    protected static AtomicLong mDbIdentifier = new AtomicLong();
+    static AtomicLong sDbIdentifier = new AtomicLong();
 
-    protected static long primaryVersionCounter = 0;
-    protected static long secondaryVersionCounter = 0;
-    public static long totalContacts = 0;
+    static long sPrimaryVersionCounter = 0;
+    static long sSecondaryVersionCounter = 0;
+    private static long sTotalContacts = 0;
 
     /* totalFields and totalSvcFields used to update primary/secondary version
      * counter between pbap sessions*/
-    public static long totalFields = 0;
-    public static long totalSvcFields = 0;
-    public static long contactsLastUpdated = 0;
-    public static boolean contactsLoaded = false;
+    private static long sTotalFields = 0;
+    private static long sTotalSvcFields = 0;
+    private static long sContactsLastUpdated = 0;
 
     private static class ContactData {
-        private String name;
-        private ArrayList<String> email;
-        private ArrayList<String> phone;
-        private ArrayList<String> address;
+        private String mName;
+        private ArrayList<String> mEmail;
+        private ArrayList<String> mPhone;
+        private ArrayList<String> mAddress;
 
-        public ContactData() {
-            phone = new ArrayList<String>();
-            email = new ArrayList<String>();
-            address = new ArrayList<String>();
+        ContactData() {
+            mPhone = new ArrayList<>();
+            mEmail = new ArrayList<>();
+            mAddress = new ArrayList<>();
         }
 
-        public ContactData(String name, ArrayList<String> phone, ArrayList<String> email,
+        ContactData(String name, ArrayList<String> phone, ArrayList<String> email,
                 ArrayList<String> address) {
-            this.name = name;
-            this.phone = phone;
-            this.email = email;
-            this.address = address;
+            this.mName = name;
+            this.mPhone = phone;
+            this.mEmail = email;
+            this.mAddress = address;
         }
     }
 
-    private static HashMap<String, ContactData> contactDataset = new HashMap<String, ContactData>();
+    private static HashMap<String, ContactData> sContactDataset = new HashMap<>();
 
-    private static HashSet<String> ContactSet = new HashSet<String>();
+    private static HashSet<String> sContactSet = new HashSet<>();
 
     private static final String TYPE_NAME = "name";
     private static final String TYPE_PHONE = "phone";
     private static final String TYPE_EMAIL = "email";
     private static final String TYPE_ADDRESS = "address";
 
-    public static boolean hasFilter(byte[] filter) {
+    private static boolean hasFilter(byte[] filter) {
         return filter != null && filter.length > 0;
     }
 
-    public static boolean isNameAndNumberOnly(byte[] filter) {
-        // For vcard 2.0: VERSION,N,TEL is mandatory
-        // For vcard 3.0, VERSION,N,FN,TEL is mandatory
-        // So we only need to make sure that no other fields except optionally
-        // NICKNAME is set
-
-        // Check that an explicit filter is not set. If not, this means
-        // return everything
-        if (!hasFilter(filter)) {
-            Log.v(TAG, "No filter set. isNameAndNumberOnly=false");
-            return false;
-        }
-
-        // Check bytes 0-4 are all 0
-        for (int i = 0; i <= 4; i++) {
-            if (filter[i] != 0) {
-                return false;
-            }
-        }
-        // On byte 5, only BIT_NICKNAME can be set, so make sure
-        // rest of bits are not set
-        if ((filter[5] & 0x7F) > 0) {
-            return false;
-        }
-
-        // Check byte 6 is not set
-        if (filter[6] != 0) {
-            return false;
-        }
-
-        // Check if bit#3-6 is set. Return false if so.
-        if ((filter[7] & 0x78) > 0) {
-            return false;
-        }
-
-        return true;
-    }
-
-    public static boolean isFilterBitSet(byte[] filter, int filterBit) {
+    private static boolean isFilterBitSet(byte[] filter, int filterBit) {
         if (hasFilter(filter)) {
             int byteNumber = 7 - filterBit / 8;
             int bitNumber = filterBit % 8;
@@ -159,114 +110,73 @@
         return false;
     }
 
-    public static VCardComposer createFilteredVCardComposer(final Context ctx,
-            final int vcardType, final byte[] filter) {
+    static VCardComposer createFilteredVCardComposer(final Context ctx, final int vcardType,
+            final byte[] filter) {
         int vType = vcardType;
-        boolean includePhoto = BluetoothPbapConfig.includePhotosInVcard()
-                    && (!hasFilter(filter) || isFilterBitSet(filter, FILTER_PHOTO));
+        boolean includePhoto =
+                BluetoothPbapConfig.includePhotosInVcard() && (!hasFilter(filter) || isFilterBitSet(
+                        filter, FILTER_PHOTO));
         if (!includePhoto) {
-            if (V) Log.v(TAG, "Excluding images from VCardComposer...");
+            if (V) {
+                Log.v(TAG, "Excluding images from VCardComposer...");
+            }
             vType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
         }
         return new VCardComposer(ctx, vType, true);
     }
 
-    public static boolean isProfileSet(Context context) {
-        Cursor c = context.getContentResolver().query(
-                Profile.CONTENT_VCARD_URI, new String[] { Profile._ID }, null,
-                null, null);
-        boolean isSet = (c != null && c.getCount() > 0);
-        if (c != null) {
-            c.close();
-            c = null;
-        }
-        return isSet;
-    }
-
     public static String getProfileName(Context context) {
-        Cursor c = context.getContentResolver().query(
-                Profile.CONTENT_URI, new String[] { Profile.DISPLAY_NAME}, null,
-                null, null);
-        String ownerName =null;
-        if (c!= null && c.moveToFirst()) {
+        Cursor c = context.getContentResolver()
+                .query(Profile.CONTENT_URI, new String[]{Profile.DISPLAY_NAME}, null, null, null);
+        String ownerName = null;
+        if (c != null && c.moveToFirst()) {
             ownerName = c.getString(0);
         }
         if (c != null) {
             c.close();
-            c = null;
         }
         return ownerName;
     }
-    public static final String createProfileVCard(Context ctx, final int vcardType,final byte[] filter) {
+
+    static String createProfileVCard(Context ctx, final int vcardType, final byte[] filter) {
         VCardComposer composer = null;
         String vcard = null;
         try {
             composer = createFilteredVCardComposer(ctx, vcardType, filter);
-            if (composer
-                    .init(Profile.CONTENT_URI, null, null, null, null, Uri
-                            .withAppendedPath(Profile.CONTENT_URI,
-                                    RawContactsEntity.CONTENT_URI
-                                            .getLastPathSegment()))) {
+            if (composer.init(Profile.CONTENT_URI, null, null, null, null,
+                    Uri.withAppendedPath(Profile.CONTENT_URI,
+                            RawContactsEntity.CONTENT_URI.getLastPathSegment()))) {
                 vcard = composer.createOneEntry();
             } else {
-                Log.e(TAG,
-                        "Unable to create profile vcard. Error initializing composer: "
-                                + composer.getErrorReason());
+                Log.e(TAG, "Unable to create profile vcard. Error initializing composer: "
+                        + composer.getErrorReason());
             }
         } catch (Throwable t) {
             Log.e(TAG, "Unable to create profile vcard.", t);
         }
         if (composer != null) {
-            try {
-                composer.terminate();
-            } catch (Throwable t) {
-
-            }
+            composer.terminate();
         }
         return vcard;
     }
 
-    public static boolean createProfileVCardFile(File file, Context context) {
-        FileInputStream is = null;
-        FileOutputStream os = null;
-        boolean success = true;
-        try {
-            AssetFileDescriptor fd = context.getContentResolver()
-                    .openAssetFileDescriptor(Profile.CONTENT_VCARD_URI, "r");
-
-            if(fd == null)
-            {
-                return false;
-            }
-            is = fd.createInputStream();
-            os = new FileOutputStream(file);
-            Utils.copyStream(is, os, 200);
-        } catch (Throwable t) {
-            Log.e(TAG, "Unable to create default contact vcard file", t);
-            success = false;
-        }
-        Utils.safeCloseStream(is);
-        Utils.safeCloseStream(os);
-        return success;
-    }
-
-    protected static void savePbapParams(Context ctx, long primaryCounter, long secondaryCounter,
-            long dbIdentifier, long lastUpdatedTimestamp, long totalFields, long totalSvcFields,
-            long totalContacts) {
+    static void savePbapParams(Context ctx) {
         SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(ctx);
+        long dbIdentifier = sDbIdentifier.get();
         Editor edit = pref.edit();
-        edit.putLong("primary", primaryCounter);
-        edit.putLong("secondary", secondaryCounter);
+        edit.putLong("primary", sPrimaryVersionCounter);
+        edit.putLong("secondary", sSecondaryVersionCounter);
         edit.putLong("dbIdentifier", dbIdentifier);
-        edit.putLong("totalContacts", totalContacts);
-        edit.putLong("lastUpdatedTimestamp", lastUpdatedTimestamp);
-        edit.putLong("totalFields", totalFields);
-        edit.putLong("totalSvcFields", totalSvcFields);
+        edit.putLong("totalContacts", sTotalContacts);
+        edit.putLong("lastUpdatedTimestamp", sContactsLastUpdated);
+        edit.putLong("totalFields", sTotalFields);
+        edit.putLong("totalSvcFields", sTotalSvcFields);
         edit.apply();
 
-        if (V)
-            Log.v(TAG, "Saved Primary:" + primaryCounter + ", Secondary:" + secondaryCounter
-                            + ", Database Identifier: " + dbIdentifier);
+        if (V) {
+            Log.v(TAG, "Saved Primary:" + sPrimaryVersionCounter + ", Secondary:"
+                    + sSecondaryVersionCounter + ", Database Identifier: " + dbIdentifier);
+        }
     }
 
     /* fetchPbapParams() loads preserved value of Database Identifiers and folder
@@ -274,222 +184,220 @@
      * one at each connection will not benefit from the resulting performance and
      * user experience improvements. So database identifier is set with current
      * timestamp and updated on rollover of folder version counter.*/
-    protected static void fetchPbapParams(Context ctx) {
+    static void fetchPbapParams(Context ctx) {
         SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(ctx);
         long timeStamp = Calendar.getInstance().getTimeInMillis();
-        BluetoothPbapUtils.mDbIdentifier.set(pref.getLong("mDbIdentifier", timeStamp));
-        BluetoothPbapUtils.primaryVersionCounter = pref.getLong("primary", 0);
-        BluetoothPbapUtils.secondaryVersionCounter = pref.getLong("secondary", 0);
-        BluetoothPbapUtils.totalFields = pref.getLong("totalContacts", 0);
-        BluetoothPbapUtils.contactsLastUpdated = pref.getLong("lastUpdatedTimestamp", timeStamp);
-        BluetoothPbapUtils.totalFields = pref.getLong("totalFields", 0);
-        BluetoothPbapUtils.totalSvcFields = pref.getLong("totalSvcFields", 0);
-        if (V) Log.v(TAG, " fetchPbapParams " + pref.getAll());
-    }
-
-    /* loadAllContacts() fetches data like name,phone,email or addrees related to
-     * all contacts. It is required to determine which field of the contact is
-     * added/updated/deleted to increment secondary version counter accordingly.*/
-    protected static void loadAllContacts(Context mContext, Handler mHandler) {
-        if (V) Log.v(TAG, "Loading Contacts ...");
-
-        try {
-            String[] projection = {Data.CONTACT_ID, Data.DATA1, Data.MIMETYPE};
-            int contactCount = 0;
-            if ((contactCount = fetchAndSetContacts(
-                         mContext, mHandler, projection, null, null, true))
-                    < 0)
-                return;
-            totalContacts = contactCount; // to set total contacts count fetched on Connect
-            contactsLoaded = true;
-        } catch (Exception e) {
-            Log.e(TAG, "Exception occurred in load contacts: " + e);
+        BluetoothPbapUtils.sDbIdentifier.set(pref.getLong("DbIdentifier", timeStamp));
+        BluetoothPbapUtils.sPrimaryVersionCounter = pref.getLong("primary", 0);
+        BluetoothPbapUtils.sSecondaryVersionCounter = pref.getLong("secondary", 0);
+        BluetoothPbapUtils.sTotalFields = pref.getLong("totalContacts", 0);
+        BluetoothPbapUtils.sContactsLastUpdated = pref.getLong("lastUpdatedTimestamp", timeStamp);
+        BluetoothPbapUtils.sTotalFields = pref.getLong("totalFields", 0);
+        BluetoothPbapUtils.sTotalSvcFields = pref.getLong("totalSvcFields", 0);
+        if (V) {
+            Log.v(TAG, " fetchPbapParams " + pref.getAll());
         }
     }
 
-    protected static void updateSecondaryVersionCounter(Context mContext, Handler mHandler) {
-        try {
-            /* updated_list stores list of contacts which are added/updated after
+    static void loadAllContacts(Context context, Handler handler) {
+        if (V) {
+            Log.v(TAG, "Loading Contacts ...");
+        }
+
+        String[] projection = {Data.CONTACT_ID, Data.DATA1, Data.MIMETYPE};
+        sTotalContacts = fetchAndSetContacts(context, handler, projection, null, null, true);
+        if (sTotalContacts < 0) {
+            sTotalContacts = 0;
+            return;
+        }
+        handler.sendMessage(handler.obtainMessage(BluetoothPbapService.CONTACTS_LOADED));
+    }
+
+    static void updateSecondaryVersionCounter(Context context, Handler handler) {
+            /* updatedList stores list of contacts which are added/updated after
              * the time when contacts were last updated. (contactsLastUpdated
              * indicates the time when contact/contacts were last updated and
              * corresponding changes were reflected in Folder Version Counters).*/
-            ArrayList<String> updated_list = new ArrayList<String>();
-            HashSet<String> currentContactSet = new HashSet<String>();
-            int currentContactCount = 0;
+        ArrayList<String> updatedList = new ArrayList<>();
+        HashSet<String> currentContactSet = new HashSet<>();
 
-            String[] projection = {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP};
-            Cursor c = mContext.getContentResolver().query(
-                    Contacts.CONTENT_URI, projection, null, null, null);
+        String[] projection = {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP};
+        Cursor c = context.getContentResolver()
+                .query(Contacts.CONTENT_URI, projection, null, null, null);
 
-            if (c == null) {
-                Log.d(TAG, "Failed to fetch data from contact database");
-                return;
+        if (c == null) {
+            Log.d(TAG, "Failed to fetch data from contact database");
+            return;
+        }
+        while (c.moveToNext()) {
+            String contactId = c.getString(0);
+            long lastUpdatedTime = c.getLong(1);
+            if (lastUpdatedTime > sContactsLastUpdated) {
+                updatedList.add(contactId);
             }
-            while (c.moveToNext()) {
-                String contactId = c.getString(0);
-                long lastUpdatedTime = c.getLong(1);
-                if (lastUpdatedTime > contactsLastUpdated) {
-                    updated_list.add(contactId);
-                }
-                currentContactSet.add(contactId);
-            }
-            currentContactCount = c.getCount();
-            c.close();
+            currentContactSet.add(contactId);
+        }
+        int currentContactCount = c.getCount();
+        c.close();
 
-            if (V) Log.v(TAG, "updated list =" + updated_list);
-            String[] dataProjection = {Data.CONTACT_ID, Data.DATA1, Data.MIMETYPE};
+        if (V) {
+            Log.v(TAG, "updated list =" + updatedList);
+        }
+        String[] dataProjection = {Data.CONTACT_ID, Data.DATA1, Data.MIMETYPE};
 
-            String whereClause = Data.CONTACT_ID + "=?";
+        String whereClause = Data.CONTACT_ID + "=?";
 
             /* code to check if new contact/contacts are added */
-            if (currentContactCount > totalContacts) {
-                for (int i = 0; i < updated_list.size(); i++) {
-                    String[] selectionArgs = {updated_list.get(i)};
-                    fetchAndSetContacts(
-                            mContext, mHandler, dataProjection, whereClause, selectionArgs, false);
-                    secondaryVersionCounter++;
-                    primaryVersionCounter++;
-                    totalContacts = currentContactCount;
-                }
+        if (currentContactCount > sTotalContacts) {
+            for (String contact : updatedList) {
+                String[] selectionArgs = {contact};
+                fetchAndSetContacts(context, handler, dataProjection, whereClause, selectionArgs,
+                        false);
+                sSecondaryVersionCounter++;
+                sPrimaryVersionCounter++;
+                sTotalContacts = currentContactCount;
+            }
                 /* When contact/contacts are deleted */
-            } else if (currentContactCount < totalContacts) {
-                totalContacts = currentContactCount;
-                ArrayList<String> svcFields = new ArrayList<String>(
-                        Arrays.asList(StructuredName.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
-                                Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE));
-                HashSet<String> deletedContacts = new HashSet<String>(ContactSet);
-                deletedContacts.removeAll(currentContactSet);
-                primaryVersionCounter += deletedContacts.size();
-                secondaryVersionCounter += deletedContacts.size();
-                if (V) Log.v(TAG, "Deleted Contacts : " + deletedContacts);
+        } else if (currentContactCount < sTotalContacts) {
+            sTotalContacts = currentContactCount;
+            ArrayList<String> svcFields = new ArrayList<>(
+                    Arrays.asList(StructuredName.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
+                            Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE));
+            HashSet<String> deletedContacts = new HashSet<>(sContactSet);
+            deletedContacts.removeAll(currentContactSet);
+            sPrimaryVersionCounter += deletedContacts.size();
+            sSecondaryVersionCounter += deletedContacts.size();
+            if (V) {
+                Log.v(TAG, "Deleted Contacts : " + deletedContacts);
+            }
 
-                // to decrement totalFields and totalSvcFields count
-                for (String deletedContact : deletedContacts) {
-                    ContactSet.remove(deletedContact);
-                    String[] selectionArgs = {deletedContact};
-                    Cursor dataCursor = mContext.getContentResolver().query(
-                            Data.CONTENT_URI, dataProjection, whereClause, selectionArgs, null);
+            // to decrement totalFields and totalSvcFields count
+            for (String deletedContact : deletedContacts) {
+                sContactSet.remove(deletedContact);
+                String[] selectionArgs = {deletedContact};
+                Cursor dataCursor = context.getContentResolver()
+                        .query(Data.CONTENT_URI, dataProjection, whereClause, selectionArgs, null);
 
-                    if (dataCursor == null) {
-                        Log.d(TAG, "Failed to fetch data from contact database");
-                        return;
-                    }
-
-                    while (dataCursor.moveToNext()) {
-                        if (svcFields.contains(
-                                    dataCursor.getString(dataCursor.getColumnIndex(Data.MIMETYPE))))
-                            totalSvcFields--;
-                        totalFields--;
-                    }
-                    dataCursor.close();
+                if (dataCursor == null) {
+                    Log.d(TAG, "Failed to fetch data from contact database");
+                    return;
                 }
 
+                while (dataCursor.moveToNext()) {
+                    if (svcFields.contains(
+                            dataCursor.getString(dataCursor.getColumnIndex(Data.MIMETYPE)))) {
+                        sTotalSvcFields--;
+                    }
+                    sTotalFields--;
+                }
+                dataCursor.close();
+            }
+
                 /* When contacts are updated. i.e. Fields of existing contacts are
                  * added/updated/deleted */
-            } else {
-                for (int i = 0; i < updated_list.size(); i++) {
-                    primaryVersionCounter++;
-                    ArrayList<String> phone_tmp = new ArrayList<String>();
-                    ArrayList<String> email_tmp = new ArrayList<String>();
-                    ArrayList<String> address_tmp = new ArrayList<String>();
-                    String name_tmp = null, updatedCID = updated_list.get(i);
-                    boolean updated = false;
+        } else {
+            for (String contact : updatedList) {
+                sPrimaryVersionCounter++;
+                ArrayList<String> phoneTmp = new ArrayList<>();
+                ArrayList<String> emailTmp = new ArrayList<>();
+                ArrayList<String> addressTmp = new ArrayList<>();
+                String nameTmp = null;
+                boolean updated = false;
 
-                    String[] selectionArgs = {updated_list.get(i)};
-                    Cursor dataCursor = mContext.getContentResolver().query(
-                            Data.CONTENT_URI, dataProjection, whereClause, selectionArgs, null);
+                String[] selectionArgs = {contact};
+                Cursor dataCursor = context.getContentResolver()
+                        .query(Data.CONTENT_URI, dataProjection, whereClause, selectionArgs, null);
 
-                    if (dataCursor == null) {
-                        Log.d(TAG, "Failed to fetch data from contact database");
-                        return;
-                    }
-                    // fetch all updated contacts and compare with cached copy of contacts
-                    int indexData = dataCursor.getColumnIndex(Data.DATA1);
-                    int indexMimeType = dataCursor.getColumnIndex(Data.MIMETYPE);
-                    String data;
-                    String mimeType;
-                    while (dataCursor.moveToNext()) {
-                        data = dataCursor.getString(indexData);
-                        mimeType = dataCursor.getString(indexMimeType);
-                        switch (mimeType) {
-                            case Email.CONTENT_ITEM_TYPE:
-                                email_tmp.add(data);
-                                break;
-                            case Phone.CONTENT_ITEM_TYPE:
-                                phone_tmp.add(data);
-                                break;
-                            case StructuredPostal.CONTENT_ITEM_TYPE:
-                                address_tmp.add(data);
-                                break;
-                            case StructuredName.CONTENT_ITEM_TYPE:
-                                name_tmp = new String(data);
-                                break;
-                        }
-                    }
-                    ContactData cData =
-                            new ContactData(name_tmp, phone_tmp, email_tmp, address_tmp);
-                    dataCursor.close();
-
-                    if ((name_tmp == null && contactDataset.get(updatedCID).name != null)
-                            || (name_tmp != null && contactDataset.get(updatedCID).name == null)
-                            || (!(name_tmp == null && contactDataset.get(updatedCID).name == null)
-                                       && !name_tmp.equals(contactDataset.get(updatedCID).name))) {
-                        updated = true;
-                    } else if (checkFieldUpdates(contactDataset.get(updatedCID).phone, phone_tmp)) {
-                        updated = true;
-                    } else if (checkFieldUpdates(contactDataset.get(updatedCID).email, email_tmp)) {
-                        updated = true;
-                    } else if (checkFieldUpdates(
-                                       contactDataset.get(updatedCID).address, address_tmp)) {
-                        updated = true;
-                    }
-
-                    if (updated) {
-                        secondaryVersionCounter++;
-                        contactDataset.put(updatedCID, cData);
+                if (dataCursor == null) {
+                    Log.d(TAG, "Failed to fetch data from contact database");
+                    return;
+                }
+                // fetch all updated contacts and compare with cached copy of contacts
+                int indexData = dataCursor.getColumnIndex(Data.DATA1);
+                int indexMimeType = dataCursor.getColumnIndex(Data.MIMETYPE);
+                String data;
+                String mimeType;
+                while (dataCursor.moveToNext()) {
+                    data = dataCursor.getString(indexData);
+                    mimeType = dataCursor.getString(indexMimeType);
+                    switch (mimeType) {
+                        case Email.CONTENT_ITEM_TYPE:
+                            emailTmp.add(data);
+                            break;
+                        case Phone.CONTENT_ITEM_TYPE:
+                            phoneTmp.add(data);
+                            break;
+                        case StructuredPostal.CONTENT_ITEM_TYPE:
+                            addressTmp.add(data);
+                            break;
+                        case StructuredName.CONTENT_ITEM_TYPE:
+                            nameTmp = data;
+                            break;
                     }
                 }
+                ContactData cData = new ContactData(nameTmp, phoneTmp, emailTmp, addressTmp);
+                dataCursor.close();
+
+                ContactData currentContactData = sContactDataset.get(contact);
+                if (currentContactData == null) {
+                    Log.e(TAG, "Null contact in the updateList: " + contact);
+                    continue;
+                }
+
+                if (!Objects.equals(nameTmp, currentContactData.mName)) {
+                    updated = true;
+                } else if (checkFieldUpdates(currentContactData.mPhone, phoneTmp)) {
+                    updated = true;
+                } else if (checkFieldUpdates(currentContactData.mEmail, emailTmp)) {
+                    updated = true;
+                } else if (checkFieldUpdates(currentContactData.mAddress, addressTmp)) {
+                    updated = true;
+                }
+
+                if (updated) {
+                    sSecondaryVersionCounter++;
+                    sContactDataset.put(contact, cData);
+                }
             }
+        }
 
-            Log.d(TAG, "primaryVersionCounter = " + primaryVersionCounter
-                            + ", secondaryVersionCounter=" + secondaryVersionCounter);
+        Log.d(TAG,
+                "primaryVersionCounter = " + sPrimaryVersionCounter + ", secondaryVersionCounter="
+                        + sSecondaryVersionCounter);
 
-            // check if Primary/Secondary version Counter has rolled over
-            if (secondaryVersionCounter < 0 || primaryVersionCounter < 0)
-                mHandler.sendMessage(
-                        mHandler.obtainMessage(BluetoothPbapService.ROLLOVER_COUNTERS));
-        } catch (Exception e) {
-            Log.e(TAG, "Exception while updating secondary version counter:" + e);
+        // check if Primary/Secondary version Counter has rolled over
+        if (sSecondaryVersionCounter < 0 || sPrimaryVersionCounter < 0) {
+            handler.sendMessage(handler.obtainMessage(BluetoothPbapService.ROLLOVER_COUNTERS));
         }
     }
 
     /* checkFieldUpdates checks update contact fields of a particular contact.
      * Field update can be a field updated/added/deleted in an existing contact.
      * Returns true if any contact field is updated else return false. */
-    protected static boolean checkFieldUpdates(
-            ArrayList<String> oldFields, ArrayList<String> newFields) {
+    private static boolean checkFieldUpdates(ArrayList<String> oldFields,
+            ArrayList<String> newFields) {
         if (newFields != null && oldFields != null) {
             if (newFields.size() != oldFields.size()) {
-                totalSvcFields += Math.abs(newFields.size() - oldFields.size());
-                totalFields += Math.abs(newFields.size() - oldFields.size());
+                sTotalSvcFields += Math.abs(newFields.size() - oldFields.size());
+                sTotalFields += Math.abs(newFields.size() - oldFields.size());
                 return true;
             }
-            for (int i = 0; i < newFields.size(); i++) {
-                if (!oldFields.contains(newFields.get(i))) {
+            for (String newField : newFields) {
+                if (!oldFields.contains(newField)) {
                     return true;
                 }
             }
             /* when all fields of type(phone/email/address) are deleted in a given contact*/
         } else if (newFields == null && oldFields != null && oldFields.size() > 0) {
-            totalSvcFields += oldFields.size();
-            totalFields += oldFields.size();
+            sTotalSvcFields += oldFields.size();
+            sTotalFields += oldFields.size();
             return true;
 
             /* when new fields are added for a type(phone/email/address) in a contact
              * for which there were no fields of this type earliar.*/
         } else if (oldFields == null && newFields != null && newFields.size() > 0) {
-            totalSvcFields += newFields.size();
-            totalFields += newFields.size();
+            sTotalSvcFields += newFields.size();
+            sTotalFields += newFields.size();
             return true;
         }
         return false;
@@ -498,21 +406,22 @@
     /* fetchAndSetContacts reads contacts and caches them
      * isLoad = true indicates its loading all contacts
      * isLoad = false indiacates its caching recently added contact in database*/
-    protected static int fetchAndSetContacts(Context mContext, Handler mHandler,
-            String[] projection, String whereClause, String[] selectionArgs, boolean isLoad) {
+    private static int fetchAndSetContacts(Context context, Handler handler, String[] projection,
+            String whereClause, String[] selectionArgs, boolean isLoad) {
         long currentTotalFields = 0, currentSvcFieldCount = 0;
-        Cursor c = mContext.getContentResolver().query(
-                Data.CONTENT_URI, projection, whereClause, selectionArgs, null);
+        Cursor c = context.getContentResolver()
+                .query(Data.CONTENT_URI, projection, whereClause, selectionArgs, null);
 
         /* send delayed message to loadContact when ContentResolver is unable
          * to fetch data from contact database using the specified URI at that
          * moment (Case: immediate Pbap connect on system boot with BT ON)*/
         if (c == null) {
             Log.d(TAG, "Failed to fetch contacts data from database..");
-            if (isLoad)
-                mHandler.sendMessageDelayed(
-                        mHandler.obtainMessage(BluetoothPbapService.LOAD_CONTACTS),
+            if (isLoad) {
+                handler.sendMessageDelayed(
+                        handler.obtainMessage(BluetoothPbapService.LOAD_CONTACTS),
                         QUERY_CONTACT_RETRY_INTERVAL);
+            }
             return -1;
         }
 
@@ -543,34 +452,38 @@
                     currentSvcFieldCount++;
                     break;
             }
-            ContactSet.add(contactId);
+            sContactSet.add(contactId);
             currentTotalFields++;
         }
         c.close();
 
         /* This code checks if there is any update in contacts after last pbap
          * disconnect has happenned (even if BT is turned OFF during this time)*/
-        if (isLoad && currentTotalFields != totalFields) {
-            primaryVersionCounter += Math.abs(totalContacts - ContactSet.size());
+        if (isLoad && currentTotalFields != sTotalFields) {
+            sPrimaryVersionCounter += Math.abs(sTotalContacts - sContactSet.size());
 
-            if (currentSvcFieldCount != totalSvcFields)
-                if (totalContacts != ContactSet.size())
-                    secondaryVersionCounter += Math.abs(totalContacts - ContactSet.size());
-                else
-                    secondaryVersionCounter++;
-            if (primaryVersionCounter < 0 || secondaryVersionCounter < 0) rolloverCounters();
+            if (currentSvcFieldCount != sTotalSvcFields) {
+                if (sTotalContacts != sContactSet.size()) {
+                    sSecondaryVersionCounter += Math.abs(sTotalContacts - sContactSet.size());
+                } else {
+                    sSecondaryVersionCounter++;
+                }
+            }
+            if (sPrimaryVersionCounter < 0 || sSecondaryVersionCounter < 0) {
+                rolloverCounters();
+            }
 
-            totalFields = currentTotalFields;
-            totalSvcFields = currentSvcFieldCount;
-            contactsLastUpdated = System.currentTimeMillis();
+            sTotalFields = currentTotalFields;
+            sTotalSvcFields = currentSvcFieldCount;
+            sContactsLastUpdated = System.currentTimeMillis();
             Log.d(TAG, "Contacts updated between last BT OFF and current"
-                            + "Pbap Connect, primaryVersionCounter=" + primaryVersionCounter
-                            + ", secondaryVersionCounter=" + secondaryVersionCounter);
+                    + "Pbap Connect, primaryVersionCounter=" + sPrimaryVersionCounter
+                    + ", secondaryVersionCounter=" + sSecondaryVersionCounter);
         } else if (!isLoad) {
-            totalFields++;
-            totalSvcFields++;
+            sTotalFields++;
+            sTotalSvcFields++;
         }
-        return ContactSet.size();
+        return sContactSet.size();
     }
 
     /* setContactFields() is used to store contacts data in local cache (phone,
@@ -578,37 +491,40 @@
      * contactsFieldData - List of field data for phone/email/address.
      * contactId - Contact ID, data1 - field value from data table for phone/email/address*/
 
-    protected static void setContactFields(String fieldType, String contactId, String data) {
-        ContactData cData = null;
-        if (contactDataset.containsKey(contactId))
-            cData = contactDataset.get(contactId);
-        else
+    private static void setContactFields(String fieldType, String contactId, String data) {
+        ContactData cData;
+        if (sContactDataset.containsKey(contactId)) {
+            cData = sContactDataset.get(contactId);
+        } else {
             cData = new ContactData();
+        }
 
         switch (fieldType) {
             case TYPE_NAME:
-                cData.name = data;
+                cData.mName = data;
                 break;
             case TYPE_PHONE:
-                cData.phone.add(data);
+                cData.mPhone.add(data);
                 break;
             case TYPE_EMAIL:
-                cData.email.add(data);
+                cData.mEmail.add(data);
                 break;
             case TYPE_ADDRESS:
-                cData.address.add(data);
+                cData.mAddress.add(data);
                 break;
         }
-        contactDataset.put(contactId, cData);
+        sContactDataset.put(contactId, cData);
     }
 
     /* As per Pbap 1.2 specification, Database Identifies shall be
      * re-generated when a Folder Version Counter rolls over or starts over.*/
 
-    protected static void rolloverCounters() {
-        mDbIdentifier.set(Calendar.getInstance().getTimeInMillis());
-        primaryVersionCounter = (primaryVersionCounter < 0) ? 0 : primaryVersionCounter;
-        secondaryVersionCounter = (secondaryVersionCounter < 0) ? 0 : secondaryVersionCounter;
-        if (V) Log.v(TAG, "mDbIdentifier rolled over to:" + mDbIdentifier);
+    static void rolloverCounters() {
+        sDbIdentifier.set(Calendar.getInstance().getTimeInMillis());
+        sPrimaryVersionCounter = (sPrimaryVersionCounter < 0) ? 0 : sPrimaryVersionCounter;
+        sSecondaryVersionCounter = (sSecondaryVersionCounter < 0) ? 0 : sSecondaryVersionCounter;
+        if (V) {
+            Log.v(TAG, "DbIdentifier rolled over to:" + sDbIdentifier);
+        }
     }
 }
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
index b28ec77..e58fa2e 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -33,12 +33,6 @@
 
 package com.android.bluetooth.pbap;
 
-import com.android.bluetooth.R;
-import com.android.bluetooth.util.DevicePolicyUtils;
-import com.android.vcard.VCardComposer;
-import com.android.vcard.VCardConfig;
-import com.android.vcard.VCardPhoneNumberTranslationCallback;
-
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
@@ -56,17 +50,18 @@
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.util.Log;
-import java.nio.ByteBuffer;
-import java.util.Collections;
-import java.util.Comparator;
+
 import com.android.bluetooth.R;
+import com.android.bluetooth.util.DevicePolicyUtils;
 import com.android.vcard.VCardComposer;
 import com.android.vcard.VCardConfig;
 import com.android.vcard.VCardPhoneNumberTranslationCallback;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Collections;
 
 import javax.obex.Operation;
 import javax.obex.ResponseCodes;
@@ -85,12 +80,12 @@
 
     static final String SORT_ORDER_PHONE_NUMBER = CommonDataKinds.Phone.NUMBER + " ASC";
 
-    static final String[] PHONES_CONTACTS_PROJECTION = new String[] {
+    static final String[] PHONES_CONTACTS_PROJECTION = new String[]{
             Phone.CONTACT_ID, // 0
             Phone.DISPLAY_NAME, // 1
     };
 
-    static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
+    static final String[] PHONE_LOOKUP_PROJECTION = new String[]{
             PhoneLookup._ID, PhoneLookup.DISPLAY_NAME
     };
 
@@ -98,7 +93,7 @@
 
     static final int CONTACTS_NAME_COLUMN_INDEX = 1;
 
-    static long LAST_FETCHED_TIME_STAMP;
+    static long sLastFetchedTimeStamp;
 
     // call histories use dynamic handles, and handles should order by date; the
     // most recently one should be the first handle. In table "calls", _id and
@@ -111,7 +106,7 @@
     public BluetoothPbapVcardManager(final Context context) {
         mContext = context;
         mResolver = mContext.getContentResolver();
-        LAST_FETCHED_TIME_STAMP = System.currentTimeMillis();
+        sLastFetchedTimeStamp = System.currentTimeMillis();
     }
 
     /**
@@ -119,8 +114,8 @@
      * @param vcardType21
      * @return
      */
-    private final String getOwnerPhoneNumberVcardFromProfile(
-            final boolean vcardType21, final byte[] filter) {
+    private String getOwnerPhoneNumberVcardFromProfile(final boolean vcardType21,
+            final byte[] filter) {
         // Currently only support Generic Vcard 2.1 and 3.0
         int vcardType;
         if (vcardType21) {
@@ -133,7 +128,7 @@
             vcardType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
         }
 
-        return BluetoothPbapUtils.createProfileVCard(mContext, vcardType,filter);
+        return BluetoothPbapUtils.createProfileVCard(mContext, vcardType, filter);
     }
 
     public final String getOwnerPhoneNumberVcard(final boolean vcardType21, final byte[] filter) {
@@ -164,7 +159,9 @@
                 size = getCallHistorySize(type);
                 break;
         }
-        if (V) Log.v(TAG, "getPhonebookSize size = " + size + " type = " + type);
+        if (V) {
+            Log.v(TAG, "getPhonebookSize size = " + size + " type = " + type);
+        }
         return size;
     }
 
@@ -172,8 +169,8 @@
         final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
         Cursor contactCursor = null;
         try {
-            contactCursor = mResolver.query(
-                    myUri, new String[] {Phone.CONTACT_ID}, null, null, Phone.CONTACT_ID);
+            contactCursor = mResolver.query(myUri, new String[]{Phone.CONTACT_ID}, null, null,
+                    Phone.CONTACT_ID);
             if (contactCursor == null) {
                 return 0;
             }
@@ -194,8 +191,8 @@
         int size = 0;
         Cursor callCursor = null;
         try {
-            callCursor = mResolver.query(myUri, null, selection, null,
-                    CallLog.Calls.DEFAULT_SORT_ORDER);
+            callCursor =
+                    mResolver.query(myUri, null, selection, null, CallLog.Calls.DEFAULT_SORT_ORDER);
             if (callCursor != null) {
                 size = callCursor.getCount();
             }
@@ -210,29 +207,29 @@
         return size;
     }
 
+    private static final int CALLS_NUMBER_COLUMN_INDEX = 0;
+    private static final int CALLS_NAME_COLUMN_INDEX = 1;
+    private static final int CALLS_NUMBER_PRESENTATION_COLUMN_INDEX = 2;
+
     public final ArrayList<String> loadCallHistoryList(final int type) {
         final Uri myUri = CallLog.Calls.CONTENT_URI;
         String selection = BluetoothPbapObexServer.createSelectionPara(type);
-        String[] projection = new String[] {
+        String[] projection = new String[]{
                 Calls.NUMBER, Calls.CACHED_NAME, Calls.NUMBER_PRESENTATION
         };
-        final int CALLS_NUMBER_COLUMN_INDEX = 0;
-        final int CALLS_NAME_COLUMN_INDEX = 1;
-        final int CALLS_NUMBER_PRESENTATION_COLUMN_INDEX = 2;
+
 
         Cursor callCursor = null;
         ArrayList<String> list = new ArrayList<String>();
         try {
-            callCursor = mResolver.query(myUri, projection, selection, null,
-                    CALLLOG_SORT_ORDER);
+            callCursor = mResolver.query(myUri, projection, selection, null, CALLLOG_SORT_ORDER);
             if (callCursor != null) {
-                for (callCursor.moveToFirst(); !callCursor.isAfterLast();
-                        callCursor.moveToNext()) {
+                for (callCursor.moveToFirst(); !callCursor.isAfterLast(); callCursor.moveToNext()) {
                     String name = callCursor.getString(CALLS_NAME_COLUMN_INDEX);
                     if (TextUtils.isEmpty(name)) {
                         // name not found, use number instead
-                        final int numberPresentation = callCursor.getInt(
-                                CALLS_NUMBER_PRESENTATION_COLUMN_INDEX);
+                        final int numberPresentation =
+                                callCursor.getInt(CALLS_NUMBER_PRESENTATION_COLUMN_INDEX);
                         if (numberPresentation != Calls.PRESENTATION_ALLOWED) {
                             name = mContext.getString(R.string.unknownNumber);
                         } else {
@@ -260,7 +257,7 @@
         if (BluetoothPbapConfig.useProfileForOwnerVcard()) {
             ownerName = BluetoothPbapUtils.getProfileName(mContext);
         }
-        if (ownerName == null || ownerName.length()==0) {
+        if (ownerName == null || ownerName.length() == 0) {
             ownerName = BluetoothPbapService.getLocalPhoneName();
         }
         nameList.add(ownerName);
@@ -276,8 +273,7 @@
             }
             contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, orderBy);
             if (contactCursor != null) {
-                appendDistinctNameIdList(nameList,
-                        mContext.getString(android.R.string.unknownName),
+                appendDistinctNameIdList(nameList, mContext.getString(android.R.string.unknownName),
                         contactCursor);
             }
         } catch (CursorWindowAllocationException e) {
@@ -310,10 +306,11 @@
         composer = BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType, null);
         composer.setPhoneNumberTranslationCallback(new VCardPhoneNumberTranslationCallback() {
 
-            public String onValueReceived(
-                    String rawValue, int type, String label, boolean isPrimary) {
+            @Override
+            public String onValueReceived(String rawValue, int type, String label,
+                    boolean isPrimary) {
                 String numberWithControlSequence = rawValue.replace(PhoneNumberUtils.PAUSE, 'p')
-                                                           .replace(PhoneNumberUtils.WAIT, 'w');
+                        .replace(PhoneNumberUtils.WAIT, 'w');
                 return numberWithControlSequence;
             }
         });
@@ -332,12 +329,12 @@
         final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
         Cursor contactCursor = null;
         try {
-            contactCursor = mResolver.query(
-                    myUri, PHONES_CONTACTS_PROJECTION, null, null, Phone.CONTACT_ID);
+            contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null,
+                    Phone.CONTACT_ID);
 
             if (contactCursor != null) {
-                if (!composer.initWithCallback(
-                            contactCursor, new EnterpriseRawContactEntitlesInfoCallback())) {
+                if (!composer.initWithCallback(contactCursor,
+                        new EnterpriseRawContactEntitlesInfoCallback())) {
                     return nameList;
                 }
 
@@ -345,12 +342,17 @@
                     String vcard = composer.createOneEntry();
                     if (vcard == null) {
                         Log.e(TAG, "Failed to read a contact. Error reason: "
-                                        + composer.getErrorReason());
+                                + composer.getErrorReason());
                         return nameList;
+                    } else if (vcard.isEmpty()) {
+                        Log.i(TAG, "Contact may have been deleted during operation");
+                        continue;
                     }
-                    if (V) Log.v(TAG, "Checking selected bits in the vcard composer" + vcard);
+                    if (V) {
+                        Log.v(TAG, "Checking selected bits in the vcard composer" + vcard);
+                    }
 
-                    if (!vcardselector.CheckVcardSelector(vcard, vcardselectorop)) {
+                    if (!vcardselector.checkVCardSelector(vcard, vcardselectorop)) {
                         Log.e(TAG, "vcard selector check fail");
                         vcard = null;
                         pbSize--;
@@ -364,10 +366,14 @@
                     }
                 }
                 if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_INDEXED) {
-                    if (V) Log.v(TAG, "getPhonebookNameList, order by index");
+                    if (V) {
+                        Log.v(TAG, "getPhonebookNameList, order by index");
+                    }
                     // Do not need to do anything, as we sort it by index already
                 } else if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
-                    if (V) Log.v(TAG, "getPhonebookNameList, order by alpha");
+                    if (V) {
+                        Log.v(TAG, "getPhonebookNameList, order by alpha");
+                    }
                     Collections.sort(nameList);
                 }
             }
@@ -394,8 +400,7 @@
             uri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
             projection = PHONES_CONTACTS_PROJECTION;
         } else {
-            uri = Uri.withAppendedPath(getPhoneLookupFilterUri(),
-                Uri.encode(phoneNumber));
+            uri = Uri.withAppendedPath(getPhoneLookupFilterUri(), Uri.encode(phoneNumber));
             projection = PHONE_LOOKUP_PROJECTION;
         }
 
@@ -403,8 +408,7 @@
             contactCursor = mResolver.query(uri, projection, null, null, Phone.CONTACT_ID);
 
             if (contactCursor != null) {
-                appendDistinctNameIdList(nameList,
-                        mContext.getString(android.R.string.unknownName),
+                appendDistinctNameIdList(nameList, mContext.getString(android.R.string.unknownName),
                         contactCursor);
                 if (V) {
                     for (String nameIdStr : nameList) {
@@ -423,8 +427,9 @@
         int tempListSize = tempNameList.size();
         for (int index = 0; index < tempListSize; index++) {
             String object = tempNameList.get(index);
-            if (!nameList.contains(object))
+            if (!nameList.contains(object)) {
                 nameList.add(object);
+            }
         }
 
         return nameList;
@@ -433,9 +438,9 @@
     byte[] getCallHistoryPrimaryFolderVersion(final int type) {
         final Uri myUri = CallLog.Calls.CONTENT_URI;
         String selection = BluetoothPbapObexServer.createSelectionPara(type);
-        selection = selection + " AND date >= " + LAST_FETCHED_TIME_STAMP;
+        selection = selection + " AND date >= " + sLastFetchedTimeStamp;
 
-        Log.d(TAG, "LAST_FETCHED_TIME_STAMP is " + LAST_FETCHED_TIME_STAMP);
+        Log.d(TAG, "LAST_FETCHED_TIME_STAMP is " + sLastFetchedTimeStamp);
         Cursor callCursor = null;
         long count = 0;
         long primaryVcMsb = 0;
@@ -454,15 +459,20 @@
             }
         }
 
-        LAST_FETCHED_TIME_STAMP = System.currentTimeMillis();
+        sLastFetchedTimeStamp = System.currentTimeMillis();
         Log.d(TAG, "getCallHistoryPrimaryFolderVersion count is " + count + " type is " + type);
         ByteBuffer pvc = ByteBuffer.allocate(16);
         pvc.putLong(primaryVcMsb);
-        Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.primaryVersionCounter);
+        Log.d(TAG, "primaryVersionCounter is " + BluetoothPbapUtils.sPrimaryVersionCounter);
         pvc.putLong(count);
         return pvc.array();
     }
 
+    private static final String[] CALLLOG_PROJECTION = new String[]{
+            CallLog.Calls._ID, // 0
+    };
+    private static final int ID_COLUMN_INDEX = 0;
+
     final int composeAndSendSelectedCallLogVcards(final int type, Operation op,
             final int startPoint, final int endPoint, final boolean vcardType21, int needSendBody,
             int pbSize, boolean ignorefilter, byte[] filter, byte[] vcardselector,
@@ -474,11 +484,6 @@
         String typeSelection = BluetoothPbapObexServer.createSelectionPara(type);
 
         final Uri myUri = CallLog.Calls.CONTENT_URI;
-        final String[] CALLLOG_PROJECTION = new String[] {
-            CallLog.Calls._ID, // 0
-        };
-        final int ID_COLUMN_INDEX = 0;
-
         Cursor callsCursor = null;
         long startPointId = 0;
         long endPointId = 0;
@@ -489,14 +494,18 @@
             if (callsCursor != null) {
                 callsCursor.moveToPosition(startPoint - 1);
                 startPointId = callsCursor.getLong(ID_COLUMN_INDEX);
-                if (V) Log.v(TAG, "Call Log query startPointId = " + startPointId);
+                if (V) {
+                    Log.v(TAG, "Call Log query startPointId = " + startPointId);
+                }
                 if (startPoint == endPoint) {
                     endPointId = startPointId;
                 } else {
                     callsCursor.moveToPosition(endPoint - 1);
                     endPointId = callsCursor.getLong(ID_COLUMN_INDEX);
                 }
-                if (V) Log.v(TAG, "Call log query endPointId = " + endPointId);
+                if (V) {
+                    Log.v(TAG, "Call log query endPointId = " + endPointId);
+                }
             }
         } catch (CursorWindowAllocationException e) {
             Log.e(TAG, "CursorWindowAllocationException while composing calllog vcards");
@@ -513,8 +522,8 @@
         } else {
             // The query to call table is by "_id DESC" order, so change
             // correspondingly.
-            recordSelection = Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<="
-                    + startPointId;
+            recordSelection =
+                    Calls._ID + ">=" + endPointId + " AND " + Calls._ID + "<=" + startPointId;
         }
 
         String selection;
@@ -524,7 +533,9 @@
             selection = "(" + typeSelection + ") AND (" + recordSelection + ")";
         }
 
-        if (V) Log.v(TAG, "Call log query selection is: " + selection);
+        if (V) {
+            Log.v(TAG, "Call log query selection is: " + selection);
+        }
 
         return composeCallLogsAndSendSelectedVCards(op, selection, vcardType21, needSendBody,
                 pbSize, null, ignorefilter, filter, vcardselector, vcardselectorop, vcardselect);
@@ -541,15 +552,15 @@
 
         final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
         Cursor contactCursor = null;
-        Cursor contactIdCursor = new MatrixCursor(new String[] {
-            Phone.CONTACT_ID
+        Cursor contactIdCursor = new MatrixCursor(new String[]{
+                Phone.CONTACT_ID
         });
         try {
-            contactCursor = mResolver.query(
-                    myUri, PHONES_CONTACTS_PROJECTION, null, null, Phone.CONTACT_ID);
+            contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null,
+                    Phone.CONTACT_ID);
             if (contactCursor != null) {
-                contactIdCursor = ContactCursorFilter.filterByRange(contactCursor, startPoint,
-                        endPoint);
+                contactIdCursor =
+                        ContactCursorFilter.filterByRange(contactCursor, startPoint, endPoint);
             }
         } catch (CursorWindowAllocationException e) {
             Log.e(TAG, "CursorWindowAllocationException while composing phonebook vcards");
@@ -559,13 +570,14 @@
             }
         }
 
-        if (vcardselect)
+        if (vcardselect) {
             return composeContactsAndSendSelectedVCards(op, contactIdCursor, vcardType21,
                     ownerVCard, needSendBody, pbSize, ignorefilter, filter, vcardselector,
                     vcardselectorop);
-        else
-            return composeContactsAndSendVCards(
-                    op, contactIdCursor, vcardType21, ownerVCard, ignorefilter, filter);
+        } else {
+            return composeContactsAndSendVCards(op, contactIdCursor, vcardType21, ownerVCard,
+                    ignorefilter, filter);
+        }
     }
 
     final int composeAndSendPhonebookOneVcard(Operation op, final int offset,
@@ -578,8 +590,8 @@
         final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
 
         Cursor contactCursor = null;
-        Cursor contactIdCursor = new MatrixCursor(new String[] {
-            Phone.CONTACT_ID
+        Cursor contactIdCursor = new MatrixCursor(new String[]{
+                Phone.CONTACT_ID
         });
         // By default order is indexed
         String orderBy = Phone.CONTACT_ID;
@@ -589,8 +601,7 @@
             }
             contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, orderBy);
         } catch (CursorWindowAllocationException e) {
-            Log.e(TAG,
-                "CursorWindowAllocationException while composing phonebook one vcard");
+            Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard");
         } finally {
             if (contactCursor != null) {
                 contactIdCursor = ContactCursorFilter.filterByOffset(contactCursor, offset);
@@ -598,8 +609,8 @@
                 contactCursor = null;
             }
         }
-        return composeContactsAndSendVCards(
-                op, contactIdCursor, vcardType21, ownerVCard, ignorefilter, filter);
+        return composeContactsAndSendVCards(op, contactIdCursor, vcardType21, ownerVCard,
+                ignorefilter, filter);
     }
 
     /**
@@ -639,7 +650,9 @@
                     previousContactId = currentContactId;
                     if (currentOffset >= startPoint) {
                         contactIdsCursor.addRow(new Long[]{currentContactId});
-                        if (V) Log.v(TAG, "contactIdsCursor.addRow: " + currentContactId);
+                        if (V) {
+                            Log.v(TAG, "contactIdsCursor.addRow: " + currentContactId);
+                        }
                     }
                     currentOffset++;
                 }
@@ -651,23 +664,26 @@
     /**
      * Handler enterprise contact id in VCardComposer
      */
-    private static class EnterpriseRawContactEntitlesInfoCallback implements
-            VCardComposer.RawContactEntitlesInfoCallback {
+    private static class EnterpriseRawContactEntitlesInfoCallback
+            implements VCardComposer.RawContactEntitlesInfoCallback {
         @Override
         public VCardComposer.RawContactEntitlesInfo getRawContactEntitlesInfo(long contactId) {
             if (Contacts.isEnterpriseContactId(contactId)) {
                 return new VCardComposer.RawContactEntitlesInfo(RawContactsEntity.CORP_CONTENT_URI,
                         contactId - Contacts.ENTERPRISE_CONTACT_ID_BASE);
             } else {
-                return new VCardComposer.RawContactEntitlesInfo(RawContactsEntity.CONTENT_URI, contactId);
+                return new VCardComposer.RawContactEntitlesInfo(RawContactsEntity.CONTENT_URI,
+                        contactId);
             }
         }
     }
 
-    private final int composeContactsAndSendVCards(Operation op, final Cursor contactIdCursor,
+    private int composeContactsAndSendVCards(Operation op, final Cursor contactIdCursor,
             final boolean vcardType21, String ownerVCard, boolean ignorefilter, byte[] filter) {
         long timestamp = 0;
-        if (V) timestamp = System.currentTimeMillis();
+        if (V) {
+            timestamp = System.currentTimeMillis();
+        }
 
         VCardComposer composer = null;
         VCardFilter vcardfilter = new VCardFilter(ignorefilter ? null : filter);
@@ -678,6 +694,8 @@
             int vcardType;
             if (vcardType21) {
                 vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;
+                vcardType |= VCardConfig.FLAG_CONVERT_PHONETIC_NAME_STRINGS;
+                vcardType |= VCardConfig.FLAG_REFRAIN_QP_TO_NAME_PROPERTIES;
             } else {
                 vcardType = VCardConfig.VCARD_TYPE_V30_GENERIC;
             }
@@ -694,23 +712,22 @@
             // other formatting
             // done by vCard library by default.
             composer.setPhoneNumberTranslationCallback(new VCardPhoneNumberTranslationCallback() {
+                @Override
                 public String onValueReceived(String rawValue, int type, String label,
                         boolean isPrimary) {
                     // 'p' and 'w' are the standard characters for pause and
                     // wait
                     // (see RFC 3601)
                     // so use those when exporting phone numbers via vCard.
-                    String numberWithControlSequence = rawValue
-                            .replace(PhoneNumberUtils.PAUSE, 'p').replace(PhoneNumberUtils.WAIT,
-                                    'w');
+                    String numberWithControlSequence = rawValue.replace(PhoneNumberUtils.PAUSE, 'p')
+                            .replace(PhoneNumberUtils.WAIT, 'w');
                     return numberWithControlSequence;
                 }
             });
             buffer = new HandlerForStringBuffer(op, ownerVCard);
             Log.v(TAG, "contactIdCursor size: " + contactIdCursor.getCount());
             if (!composer.initWithCallback(contactIdCursor,
-                    new EnterpriseRawContactEntitlesInfoCallback())
-                    || !buffer.onInit(mContext)) {
+                    new EnterpriseRawContactEntitlesInfoCallback()) || !buffer.onInit(mContext)) {
                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
             }
 
@@ -725,13 +742,20 @@
                     Log.e(TAG,
                             "Failed to read a contact. Error reason: " + composer.getErrorReason());
                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+                } else if (vcard.isEmpty()) {
+                    Log.i(TAG, "Contact may have been deleted during operation");
+                    continue;
                 }
-                if (V) Log.v(TAG, "vCard from composer: " + vcard);
+                if (V) {
+                    Log.v(TAG, "vCard from composer: " + vcard);
+                }
 
                 vcard = vcardfilter.apply(vcard, vcardType21);
-                vcard = StripTelephoneNumber(vcard);
+                vcard = stripTelephoneNumber(vcard);
 
-                if (V) Log.v(TAG, "vCard after cleanup: " + vcard);
+                if (V) {
+                    Log.v(TAG, "vCard after cleanup: " + vcard);
+                }
 
                 if (!buffer.onEntryCreated(vcard)) {
                     // onEntryCreate() already emits error.
@@ -747,18 +771,21 @@
             }
         }
 
-        if (V) Log.v(TAG, "Total vcard composing and sending out takes "
-                    + (System.currentTimeMillis() - timestamp) + " ms");
+        if (V) {
+            Log.v(TAG, "Total vcard composing and sending out takes " + (System.currentTimeMillis()
+                    - timestamp) + " ms");
+        }
 
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
-    private final int composeContactsAndSendSelectedVCards(Operation op,
-            final Cursor contactIdCursor, final boolean vcardType21, String ownerVCard,
-            int needSendBody, int pbSize, boolean ignorefilter, byte[] filter, byte[] selector,
-            String vcardselectorop) {
+    private int composeContactsAndSendSelectedVCards(Operation op, final Cursor contactIdCursor,
+            final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize,
+            boolean ignorefilter, byte[] filter, byte[] selector, String vcardselectorop) {
         long timestamp = 0;
-        if (V) timestamp = System.currentTimeMillis();
+        if (V) {
+            timestamp = System.currentTimeMillis();
+        }
 
         VCardComposer composer = null;
         VCardFilter vcardfilter = new VCardFilter(ignorefilter ? null : filter);
@@ -786,20 +813,20 @@
             /* BT does want PAUSE/WAIT conversion while it doesn't want the
              * other formatting done by vCard library by default. */
             composer.setPhoneNumberTranslationCallback(new VCardPhoneNumberTranslationCallback() {
-                public String onValueReceived(
-                        String rawValue, int type, String label, boolean isPrimary) {
+                @Override
+                public String onValueReceived(String rawValue, int type, String label,
+                        boolean isPrimary) {
                     /* 'p' and 'w' are the standard characters for pause and wait
                      * (see RFC 3601) so use those when exporting phone numbers via vCard.*/
                     String numberWithControlSequence = rawValue.replace(PhoneNumberUtils.PAUSE, 'p')
-                                                               .replace(PhoneNumberUtils.WAIT, 'w');
+                            .replace(PhoneNumberUtils.WAIT, 'w');
                     return numberWithControlSequence;
                 }
             });
             buffer = new HandlerForStringBuffer(op, ownerVCard);
             Log.v(TAG, "contactIdCursor size: " + contactIdCursor.getCount());
-            if (!composer.initWithCallback(
-                        contactIdCursor, new EnterpriseRawContactEntitlesInfoCallback())
-                    || !buffer.onInit(mContext)) {
+            if (!composer.initWithCallback(contactIdCursor,
+                    new EnterpriseRawContactEntitlesInfoCallback()) || !buffer.onInit(mContext)) {
                 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
             }
 
@@ -814,10 +841,15 @@
                     Log.e(TAG,
                             "Failed to read a contact. Error reason: " + composer.getErrorReason());
                     return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+                } else if (vcard.isEmpty()) {
+                    Log.i(TAG, "Contact may have been deleted during operation");
+                    continue;
                 }
-                if (V) Log.v(TAG, "Checking selected bits in the vcard composer" + vcard);
+                if (V) {
+                    Log.v(TAG, "Checking selected bits in the vcard composer" + vcard);
+                }
 
-                if (!vcardselector.CheckVcardSelector(vcard, vcardselectorop)) {
+                if (!vcardselector.checkVCardSelector(vcard, vcardselectorop)) {
                     Log.e(TAG, "vcard selector check fail");
                     vcard = null;
                     pbSize--;
@@ -828,9 +860,11 @@
 
                 if (needSendBody == NEED_SEND_BODY) {
                     vcard = vcardfilter.apply(vcard, vcardType21);
-                    vcard = StripTelephoneNumber(vcard);
+                    vcard = stripTelephoneNumber(vcard);
 
-                    if (V) Log.v(TAG, "vCard after cleanup: " + vcard);
+                    if (V) {
+                        Log.v(TAG, "vCard after cleanup: " + vcard);
+                    }
 
                     if (!buffer.onEntryCreated(vcard)) {
                         // onEntryCreate() already emits error.
@@ -839,7 +873,9 @@
                 }
             }
 
-            if (needSendBody != NEED_SEND_BODY) return pbSize;
+            if (needSendBody != NEED_SEND_BODY) {
+                return pbSize;
+            }
         } finally {
             if (composer != null) {
                 composer.terminate();
@@ -849,19 +885,22 @@
             }
         }
 
-        if (V)
-            Log.v(TAG, "Total vcard composing and sending out takes "
-                            + (System.currentTimeMillis() - timestamp) + " ms");
+        if (V) {
+            Log.v(TAG, "Total vcard composing and sending out takes " + (System.currentTimeMillis()
+                    - timestamp) + " ms");
+        }
 
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
-    private final int composeCallLogsAndSendSelectedVCards(Operation op, final String selection,
+    private int composeCallLogsAndSendSelectedVCards(Operation op, final String selection,
             final boolean vcardType21, int needSendBody, int pbSize, String ownerVCard,
             boolean ignorefilter, byte[] filter, byte[] selector, String vcardselectorop,
             boolean vCardSelct) {
         long timestamp = 0;
-        if (V) timestamp = System.currentTimeMillis();
+        if (V) {
+            timestamp = System.currentTimeMillis();
+        }
 
         BluetoothPbapCallLogComposer composer = null;
         HandlerForStringBuffer buffer = null;
@@ -884,21 +923,23 @@
                 }
                 String vcard = composer.createOneEntry(vcardType21);
                 if (vCardSelct) {
-                    if (!vcardselector.CheckVcardSelector(vcard, vcardselectorop)) {
+                    if (!vcardselector.checkVCardSelector(vcard, vcardselectorop)) {
                         Log.e(TAG, "Checking vcard selector for call log");
                         vcard = null;
                         pbSize--;
                         continue;
                     }
                     if (needSendBody == NEED_SEND_BODY) {
-                        if (vcard != null) {
-                            vcard = vcardfilter.apply(vcard, vcardType21);
-                        }
                         if (vcard == null) {
                             Log.e(TAG, "Failed to read a contact. Error reason: "
-                                            + composer.getErrorReason());
+                                    + composer.getErrorReason());
                             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+                        } else if (vcard.isEmpty()) {
+                            Log.i(TAG, "Call Log may have been deleted during operation");
+                            continue;
                         }
+                        vcard = vcardfilter.apply(vcard, vcardType21);
+
                         if (V) {
                             Log.v(TAG, "Vcard Entry:");
                             Log.v(TAG, vcard);
@@ -908,7 +949,7 @@
                 } else {
                     if (vcard == null) {
                         Log.e(TAG, "Failed to read a contact. Error reason: "
-                                        + composer.getErrorReason());
+                                + composer.getErrorReason());
                         return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
                     }
                     if (V) {
@@ -918,7 +959,9 @@
                     buffer.onEntryCreated(vcard);
                 }
             }
-            if (needSendBody != NEED_SEND_BODY && vCardSelct) return pbSize;
+            if (needSendBody != NEED_SEND_BODY && vCardSelct) {
+                return pbSize;
+            }
         } finally {
             if (composer != null) {
                 composer.terminate();
@@ -928,55 +971,73 @@
             }
         }
 
-        if (V) Log.v(TAG, "Total vcard composing and sending out takes "
-                + (System.currentTimeMillis() - timestamp) + " ms");
+        if (V) {
+            Log.v(TAG, "Total vcard composing and sending out takes " + (System.currentTimeMillis()
+                    - timestamp) + " ms");
+        }
         return ResponseCodes.OBEX_HTTP_OK;
     }
 
-    public String StripTelephoneNumber (String vCard){
-        String attr [] = vCard.split(System.getProperty("line.separator"));
-        String Vcard = "";
-            for (int i=0; i < attr.length; i++) {
-                if(attr[i].startsWith("TEL")) {
-                    attr[i] = attr[i].replace("(", "");
-                    attr[i] = attr[i].replace(")", "");
-                    attr[i] = attr[i].replace("-", "");
-                    attr[i] = attr[i].replace(" ", "");
+    public String stripTelephoneNumber(String vCard) {
+        String[] attr = vCard.split(System.getProperty("line.separator"));
+        String stripedVCard = "";
+        for (int i = 0; i < attr.length; i++) {
+            if (attr[i].startsWith("TEL")) {
+                String[] vTagAndTel = attr[i].split(":", 2);
+                int telLenBefore = vTagAndTel[1].length();
+                // Remove '-', '(', ')' or ' ' from TEL number
+                vTagAndTel[1] = vTagAndTel[1].replace("-", "")
+                                             .replace("(", "")
+                                             .replace(")", "")
+                                             .replace(" ", "");
+                if (vTagAndTel[1].length() < telLenBefore) {
+                    if (V) {
+                        Log.v(TAG, "Fixing vCard TEL to " + vTagAndTel[1]);
+                    }
+                    attr[i] = new StringBuilder().append(vTagAndTel[0]).append(":")
+                                                 .append(vTagAndTel[1]).toString();
                 }
             }
+        }
 
-            for (int i=0; i < attr.length; i++) {
-                if(!attr[i].equals("")){
-                    Vcard = Vcard.concat(attr[i] + "\n");
-                }
+        for (int i = 0; i < attr.length; i++) {
+            if (!attr[i].isEmpty()) {
+                stripedVCard = stripedVCard.concat(attr[i] + "\n");
             }
-        if (V) Log.v(TAG, "Vcard with stripped telephone no.: " + Vcard);
-        return Vcard;
+        }
+        if (V) {
+            Log.v(TAG, "vCard with stripped telephone no.: " + stripedVCard);
+        }
+        return stripedVCard;
     }
 
     /**
      * Handler to emit vCards to PCE.
      */
     public class HandlerForStringBuffer {
-        private Operation operation;
+        private Operation mOperation;
 
-        private OutputStream outputStream;
+        private OutputStream mOutputStream;
 
-        private String phoneOwnVCard = null;
+        private String mPhoneOwnVCard = null;
 
         public HandlerForStringBuffer(Operation op, String ownerVCard) {
-            operation = op;
+            mOperation = op;
             if (ownerVCard != null) {
-                phoneOwnVCard = ownerVCard;
-                if (V) Log.v(TAG, "phone own number vcard:");
-                if (V) Log.v(TAG, phoneOwnVCard);
+                mPhoneOwnVCard = ownerVCard;
+                if (V) {
+                    Log.v(TAG, "phone own number vcard:");
+                }
+                if (V) {
+                    Log.v(TAG, mPhoneOwnVCard);
+                }
             }
         }
 
         private boolean write(String vCard) {
             try {
                 if (vCard != null) {
-                    outputStream.write(vCard.getBytes());
+                    mOutputStream.write(vCard.getBytes());
                     return true;
                 }
             } catch (IOException e) {
@@ -987,9 +1048,9 @@
 
         public boolean onInit(Context context) {
             try {
-                outputStream = operation.openOutputStream();
-                if (phoneOwnVCard != null) {
-                    return write(phoneOwnVCard);
+                mOutputStream = mOperation.openOutputStream();
+                if (mPhoneOwnVCard != null) {
+                    return write(mPhoneOwnVCard);
                 }
                 return true;
             } catch (IOException e) {
@@ -1003,28 +1064,33 @@
         }
 
         public void onTerminate() {
-            if (!BluetoothPbapObexServer.closeStream(outputStream, operation)) {
-                if (V) Log.v(TAG, "CloseStream failed!");
+            if (!BluetoothPbapObexServer.closeStream(mOutputStream, mOperation)) {
+                if (V) {
+                    Log.v(TAG, "CloseStream failed!");
+                }
             } else {
-                if (V) Log.v(TAG, "CloseStream ok!");
+                if (V) {
+                    Log.v(TAG, "CloseStream ok!");
+                }
             }
         }
     }
 
     public static class VCardFilter {
-        private static enum FilterBit {
+        private enum FilterBit {
             //       bit  property                  onlyCheckV21  excludeForV21
-            FN (       1, "FN",                       true,         false),
-            PHOTO(     3, "PHOTO",                    false,        false),
-            BDAY(      4, "BDAY",                     false,        false),
-            ADR(       5, "ADR",                      false,        false),
-            EMAIL(     8, "EMAIL",                    false,        false),
-            TITLE(    12, "TITLE",                    false,        false),
-            ORG(      16, "ORG",                      false,        false),
-            NOTE(     17, "NOTE",                     false,        false),
-            URL(      20, "URL",                      false,        false),
-            NICKNAME( 23, "NICKNAME",                 false,        true),
-            DATETIME( 28, "X-IRMC-CALL-DATETIME",     false,        false);
+            FN(1, "FN", true, false),
+            PHOTO(3, "PHOTO", false, false),
+            BDAY(4, "BDAY", false, false),
+            ADR(5, "ADR", false, false),
+            EMAIL(8, "EMAIL", false, false),
+            TITLE(12, "TITLE", false, false),
+            ORG(16, "ORG", false, false),
+            NOTE(17, "NOTE", false, false),
+            SOUND(19, "SOUND", false, false),
+            URL(20, "URL", false, false),
+            NICKNAME(23, "NICKNAME", false, true),
+            DATETIME(28, "X-IRMC-CALL-DATETIME", false, false);
 
             public final int pos;
             public final String prop;
@@ -1040,29 +1106,37 @@
         }
 
         private static final String SEPARATOR = System.getProperty("line.separator");
-        private final byte[] filter;
+        private final byte[] mFilter;
 
         //This function returns true if the attributes needs to be included in the filtered vcard.
         private boolean isFilteredIn(FilterBit bit, boolean vCardType21) {
             final int offset = (bit.pos / 8) + 1;
-            final int bit_pos = bit.pos % 8;
-            if (!vCardType21 && bit.onlyCheckV21) return true;
-            if (vCardType21 && bit.excludeForV21) return false;
-            if (filter == null || offset >= filter.length) return true;
-            return ((filter[filter.length - offset] >> bit_pos) & 0x01) != 0;
+            final int bitPos = bit.pos % 8;
+            if (!vCardType21 && bit.onlyCheckV21) {
+                return true;
+            }
+            if (vCardType21 && bit.excludeForV21) {
+                return false;
+            }
+            if (mFilter == null || offset >= mFilter.length) {
+                return true;
+            }
+            return ((mFilter[mFilter.length - offset] >> bitPos) & 0x01) != 0;
         }
 
         VCardFilter(byte[] filter) {
-            this.filter = filter;
+            this.mFilter = filter;
         }
 
         public boolean isPhotoEnabled() {
             return isFilteredIn(FilterBit.PHOTO, false);
         }
 
-        public String apply(String vCard, boolean vCardType21){
-            if (filter == null) return vCard;
-            String lines[] = vCard.split(SEPARATOR);
+        public String apply(String vCard, boolean vCardType21) {
+            if (mFilter == null) {
+                return vCard;
+            }
+            String[] lines = vCard.split(SEPARATOR);
             StringBuilder filteredVCard = new StringBuilder();
             boolean filteredIn = false;
 
@@ -1102,7 +1176,7 @@
     }
 
     private static class PropertySelector {
-        private static enum PropertyMask {
+        private enum PropertyMask {
             //               bit    property
             VERSION(0, "VERSION"),
             FN(1, "FN"),
@@ -1130,23 +1204,22 @@
         }
 
         private static final String SEPARATOR = System.getProperty("line.separator");
-        private final byte[] selector;
+        private final byte[] mSelector;
 
         PropertySelector(byte[] selector) {
-            this.selector = selector;
+            this.mSelector = selector;
         }
 
-        private boolean checkbit(int attr_bit, byte[] selector) {
+        private boolean checkbit(int attrBit, byte[] selector) {
             int selectorlen = selector.length;
-            if (((selector[selectorlen - 1 - ((int) attr_bit / 8)] >> (attr_bit % 8)) & 0x01)
-                    == 0) {
+            if (((selector[selectorlen - 1 - ((int) attrBit / 8)] >> (attrBit % 8)) & 0x01) == 0) {
                 return false;
             }
             return true;
         }
 
         private boolean checkprop(String vcard, String prop) {
-            String lines[] = vcard.split(SEPARATOR);
+            String[] lines = vcard.split(SEPARATOR);
             boolean isPresent = false;
             for (String line : lines) {
                 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) {
@@ -1162,11 +1235,11 @@
             return isPresent;
         }
 
-        private boolean CheckVcardSelector(String vcard, String vcardselectorop) {
+        private boolean checkVCardSelector(String vcard, String vcardselectorop) {
             boolean selectedIn = true;
 
             for (PropertyMask bit : PropertyMask.values()) {
-                if (checkbit(bit.pos, selector)) {
+                if (checkbit(bit.pos, mSelector)) {
                     Log.d(TAG, "checking for prop :" + bit.prop);
                     if (vcardselectorop.equals("0")) {
                         if (checkprop(vcard, bit.prop)) {
@@ -1191,12 +1264,13 @@
         }
 
         private String getName(String vcard) {
-            String lines[] = vcard.split(SEPARATOR);
+            String[] lines = vcard.split(SEPARATOR);
             String name = "";
             for (String line : lines) {
                 if (!Character.isWhitespace(line.charAt(0)) && !line.startsWith("=")) {
-                    if (line.startsWith("N:"))
+                    if (line.startsWith("N:")) {
                         name = line.substring(line.lastIndexOf(':'), line.length());
+                    }
                 }
             }
             Log.d(TAG, "returning name: " + name);
@@ -1204,7 +1278,7 @@
         }
     }
 
-    private static final Uri getPhoneLookupFilterUri() {
+    private static Uri getPhoneLookupFilterUri() {
         return PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI;
     }
 
@@ -1212,14 +1286,15 @@
      * Get size of the cursor without duplicated contact id. This assumes the
      * given cursor is sorted by CONTACT_ID.
      */
-    private static final int getDistinctContactIdSize(Cursor cursor) {
+    private static int getDistinctContactIdSize(Cursor cursor) {
         final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID);
         final int idColumn = cursor.getColumnIndex(Data._ID);
         long previousContactId = -1;
         int count = 0;
         cursor.moveToPosition(-1);
         while (cursor.moveToNext()) {
-            final long contactId = cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn);
+            final long contactId =
+                    cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn);
             if (previousContactId != contactId) {
                 count++;
                 previousContactId = contactId;
@@ -1235,14 +1310,15 @@
      * Append "display_name,contact_id" string array from cursor to ArrayList.
      * This assumes the given cursor is sorted by CONTACT_ID.
      */
-    private static void appendDistinctNameIdList(ArrayList<String> resultList,
-            String defaultName, Cursor cursor) {
+    private static void appendDistinctNameIdList(ArrayList<String> resultList, String defaultName,
+            Cursor cursor) {
         final int contactIdColumn = cursor.getColumnIndex(Data.CONTACT_ID);
         final int idColumn = cursor.getColumnIndex(Data._ID);
         final int nameColumn = cursor.getColumnIndex(Data.DISPLAY_NAME);
         cursor.moveToPosition(-1);
         while (cursor.moveToNext()) {
-            final long contactId = cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn);
+            final long contactId =
+                    cursor.getLong(contactIdColumn != -1 ? contactIdColumn : idColumn);
             String displayName = nameColumn != -1 ? cursor.getString(nameColumn) : defaultName;
             if (TextUtils.isEmpty(displayName)) {
                 displayName = defaultName;
diff --git a/src/com/android/bluetooth/pbap/PbapStateMachine.java b/src/com/android/bluetooth/pbap/PbapStateMachine.java
new file mode 100644
index 0000000..0f53be5
--- /dev/null
+++ b/src/com/android/bluetooth/pbap/PbapStateMachine.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright 2017 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.pbap;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothPbap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothObexTransport;
+import com.android.bluetooth.IObexConnectionHandler;
+import com.android.bluetooth.ObexRejectServer;
+import com.android.bluetooth.R;
+import com.android.bluetooth.btservice.MetricsLogger;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.io.IOException;
+
+import javax.obex.ResponseCodes;
+import javax.obex.ServerSession;
+
+/**
+ * Bluetooth PBAP StateMachine
+ *              (New connection socket)
+ *                 WAITING FOR AUTH
+ *                        |
+ *                        |    (request permission from Settings UI)
+ *                        |
+ *           (Accept)    / \   (Reject)
+ *                      /   \
+ *                     v     v
+ *          CONNECTED   ----->  FINISHED
+ *                (OBEX Server done)
+ */
+class PbapStateMachine extends StateMachine {
+    private static final String TAG = "PbapStateMachine";
+    private static final boolean DEBUG = true;
+    private static final boolean VERBOSE = true;
+    private static final String PBAP_OBEX_NOTIFICATION_CHANNEL = "pbap_obex_notification_channel";
+
+    static final int AUTHORIZED = 1;
+    static final int REJECTED = 2;
+    static final int DISCONNECT = 3;
+    static final int REQUEST_PERMISSION = 4;
+    static final int CREATE_NOTIFICATION = 5;
+    static final int REMOVE_NOTIFICATION = 6;
+    static final int AUTH_KEY_INPUT = 7;
+    static final int AUTH_CANCELLED = 8;
+
+    private BluetoothPbapService mService;
+    private IObexConnectionHandler mIObexConnectionHandler;
+
+    private final WaitingForAuth mWaitingForAuth = new WaitingForAuth();
+    private final Finished mFinished = new Finished();
+    private final Connected mConnected = new Connected();
+    private PbapStateBase mPrevState;
+    private BluetoothDevice mRemoteDevice;
+    private Handler mServiceHandler;
+    private BluetoothSocket mConnSocket;
+    private BluetoothPbapObexServer mPbapServer;
+    private BluetoothPbapAuthenticator mObexAuth;
+    private ServerSession mServerSession;
+    private int mNotificationId;
+
+    private PbapStateMachine(@NonNull BluetoothPbapService service, Looper looper,
+            @NonNull BluetoothDevice device, @NonNull BluetoothSocket connSocket,
+            IObexConnectionHandler obexConnectionHandler, Handler pbapHandler, int notificationId) {
+        super(TAG, looper);
+        mService = service;
+        mIObexConnectionHandler = obexConnectionHandler;
+        mRemoteDevice = device;
+        mServiceHandler = pbapHandler;
+        mConnSocket = connSocket;
+        mNotificationId = notificationId;
+
+        addState(mFinished);
+        addState(mWaitingForAuth);
+        addState(mConnected);
+        setInitialState(mWaitingForAuth);
+    }
+
+    static PbapStateMachine make(BluetoothPbapService service, Looper looper,
+            BluetoothDevice device, BluetoothSocket connSocket,
+            IObexConnectionHandler obexConnectionHandler, Handler pbapHandler, int notificationId) {
+        PbapStateMachine stateMachine =
+                new PbapStateMachine(service, looper, device, connSocket, obexConnectionHandler,
+                        pbapHandler, notificationId);
+        stateMachine.start();
+        return stateMachine;
+    }
+
+    BluetoothDevice getRemoteDevice() {
+        return mRemoteDevice;
+    }
+
+    private abstract class PbapStateBase extends State {
+        /**
+         * Get a state value from {@link BluetoothProfile} that represents the connection state of
+         * this headset state
+         *
+         * @return a value in {@link BluetoothProfile#STATE_DISCONNECTED},
+         * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
+         * {@link BluetoothProfile#STATE_DISCONNECTING}
+         */
+        abstract int getConnectionStateInt();
+
+        @Override
+        public void enter() {
+            // Crash if mPrevState is null and state is not Disconnected
+            if (!(this instanceof WaitingForAuth) && mPrevState == null) {
+                throw new IllegalStateException("mPrevState is null on entering initial state");
+            }
+            enforceValidConnectionStateTransition();
+        }
+
+        @Override
+        public void exit() {
+            mPrevState = this;
+        }
+
+        // Should not be called from enter() method
+        private void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) {
+            stateLogD("broadcastConnectionState " + device + ": " + fromState + "->" + toState);
+            Intent intent = new Intent(BluetoothPbap.ACTION_CONNECTION_STATE_CHANGED);
+            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
+            intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
+            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            mService.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    BluetoothPbapService.BLUETOOTH_PERM);
+        }
+
+        /**
+         * Broadcast connection state change for this state machine
+         */
+        void broadcastStateTransitions() {
+            int prevStateInt = BluetoothProfile.STATE_DISCONNECTED;
+            if (mPrevState != null) {
+                prevStateInt = mPrevState.getConnectionStateInt();
+            }
+            if (getConnectionStateInt() != prevStateInt) {
+                stateLogD("connection state changed: " + mRemoteDevice + ": " + mPrevState + " -> "
+                        + this);
+                broadcastConnectionState(mRemoteDevice, prevStateInt, getConnectionStateInt());
+            }
+        }
+
+        /**
+         * Verify if the current state transition is legal by design. This is called from enter()
+         * method and crash if the state transition is not expected by the state machine design.
+         *
+         * Note:
+         * This method uses state objects to verify transition because these objects should be final
+         * and any other instances are invalid
+         */
+        private void enforceValidConnectionStateTransition() {
+            boolean isValidTransition = false;
+            if (this == mWaitingForAuth) {
+                isValidTransition = mPrevState == null;
+            } else if (this == mFinished) {
+                isValidTransition = mPrevState == mConnected || mPrevState == mWaitingForAuth;
+            } else if (this == mConnected) {
+                isValidTransition = mPrevState == mFinished || mPrevState == mWaitingForAuth;
+            }
+            if (!isValidTransition) {
+                throw new IllegalStateException(
+                        "Invalid state transition from " + mPrevState + " to " + this
+                                + " for device " + mRemoteDevice);
+            }
+        }
+
+        void stateLogD(String msg) {
+            log(getName() + ": currentDevice=" + mRemoteDevice + ", msg=" + msg);
+        }
+    }
+
+    class WaitingForAuth extends PbapStateBase {
+        @Override
+        int getConnectionStateInt() {
+            return BluetoothProfile.STATE_CONNECTING;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            broadcastStateTransitions();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case REQUEST_PERMISSION:
+                    mService.checkOrGetPhonebookPermission(PbapStateMachine.this);
+                    break;
+                case AUTHORIZED:
+                    transitionTo(mConnected);
+                    break;
+                case REJECTED:
+                    rejectConnection();
+                    transitionTo(mFinished);
+                    break;
+                case DISCONNECT:
+                    mServiceHandler.removeMessages(BluetoothPbapService.USER_TIMEOUT,
+                            PbapStateMachine.this);
+                    mServiceHandler.obtainMessage(BluetoothPbapService.USER_TIMEOUT,
+                            PbapStateMachine.this).sendToTarget();
+                    transitionTo(mFinished);
+                    break;
+            }
+            return HANDLED;
+        }
+
+        private void rejectConnection() {
+            mPbapServer =
+                    new BluetoothPbapObexServer(mServiceHandler, mService, PbapStateMachine.this);
+            BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
+            ObexRejectServer server =
+                    new ObexRejectServer(ResponseCodes.OBEX_HTTP_UNAVAILABLE, mConnSocket);
+            try {
+                mServerSession = new ServerSession(transport, server, null);
+            } catch (IOException ex) {
+                Log.e(TAG, "Caught exception starting OBEX reject server session" + ex.toString());
+            }
+        }
+    }
+
+    class Finished extends PbapStateBase {
+        @Override
+        int getConnectionStateInt() {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            // Close OBEX server session
+            if (mServerSession != null) {
+                mServerSession.close();
+                mServerSession = null;
+            }
+
+            // Close connection socket
+            try {
+                mConnSocket.close();
+                mConnSocket = null;
+            } catch (IOException e) {
+                Log.e(TAG, "Close Connection Socket error: " + e.toString());
+            }
+
+            mServiceHandler.obtainMessage(BluetoothPbapService.MSG_STATE_MACHINE_DONE,
+                    PbapStateMachine.this).sendToTarget();
+            broadcastStateTransitions();
+        }
+    }
+
+    class Connected extends PbapStateBase {
+        @Override
+        int getConnectionStateInt() {
+            return BluetoothProfile.STATE_CONNECTED;
+        }
+
+        @Override
+        public void enter() {
+            try {
+                startObexServerSession();
+            } catch (IOException ex) {
+                Log.e(TAG, "Caught exception starting OBEX server session" + ex.toString());
+            }
+            broadcastStateTransitions();
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.PBAP);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            switch (message.what) {
+                case DISCONNECT:
+                    stopObexServerSession();
+                    break;
+                case CREATE_NOTIFICATION:
+                    createPbapNotification();
+                    break;
+                case REMOVE_NOTIFICATION:
+                    Intent i = new Intent(BluetoothPbapService.USER_CONFIRM_TIMEOUT_ACTION);
+                    mService.sendBroadcast(i);
+                    notifyAuthCancelled();
+                    removePbapNotification(mNotificationId);
+                    break;
+                case AUTH_KEY_INPUT:
+                    String key = (String) message.obj;
+                    notifyAuthKeyInput(key);
+                    break;
+                case AUTH_CANCELLED:
+                    notifyAuthCancelled();
+                    break;
+            }
+            return HANDLED;
+        }
+
+        private void startObexServerSession() throws IOException {
+            if (VERBOSE) {
+                Log.v(TAG, "Pbap Service startObexServerSession");
+            }
+
+            // acquire the wakeLock before start Obex transaction thread
+            mServiceHandler.sendMessage(
+                    mServiceHandler.obtainMessage(BluetoothPbapService.MSG_ACQUIRE_WAKE_LOCK));
+
+            mPbapServer =
+                    new BluetoothPbapObexServer(mServiceHandler, mService, PbapStateMachine.this);
+            synchronized (this) {
+                mObexAuth = new BluetoothPbapAuthenticator(PbapStateMachine.this);
+                mObexAuth.setChallenged(false);
+                mObexAuth.setCancelled(false);
+            }
+            BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
+            mServerSession = new ServerSession(transport, mPbapServer, mObexAuth);
+            // It's ok to just use one wake lock
+            // Message MSG_ACQUIRE_WAKE_LOCK is always surrounded by RELEASE. safe.
+        }
+
+        private void stopObexServerSession() {
+            if (VERBOSE) {
+                Log.v(TAG, "Pbap Service stopObexServerSession");
+            }
+            transitionTo(mFinished);
+        }
+
+        private void createPbapNotification() {
+            NotificationManager nm =
+                    (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
+            NotificationChannel notificationChannel =
+                    new NotificationChannel(PBAP_OBEX_NOTIFICATION_CHANNEL,
+                            mService.getString(R.string.pbap_notification_group),
+                            NotificationManager.IMPORTANCE_HIGH);
+            nm.createNotificationChannel(notificationChannel);
+
+            // Create an intent triggered by clicking on the status icon.
+            Intent clickIntent = new Intent();
+            clickIntent.setClass(mService, BluetoothPbapActivity.class);
+            clickIntent.putExtra(BluetoothPbapService.EXTRA_DEVICE, mRemoteDevice);
+            clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            clickIntent.setAction(BluetoothPbapService.AUTH_CHALL_ACTION);
+
+            // Create an intent triggered by clicking on the
+            // "Clear All Notifications" button
+            Intent deleteIntent = new Intent();
+            deleteIntent.setClass(mService, BluetoothPbapService.class);
+            deleteIntent.setAction(BluetoothPbapService.AUTH_CANCELLED_ACTION);
+
+            String name = mRemoteDevice.getName();
+
+            Notification notification =
+                    new Notification.Builder(mService, PBAP_OBEX_NOTIFICATION_CHANNEL).setWhen(
+                            System.currentTimeMillis())
+                            .setContentTitle(mService.getString(R.string.auth_notif_title))
+                            .setContentText(mService.getString(R.string.auth_notif_message, name))
+                            .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
+                            .setTicker(mService.getString(R.string.auth_notif_ticker))
+                            .setColor(mService.getResources()
+                                    .getColor(
+                                            com.android.internal.R.color
+                                                    .system_notification_accent_color,
+                                            mService.getTheme()))
+                            .setFlag(Notification.FLAG_AUTO_CANCEL, true)
+                            .setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
+                            .setContentIntent(
+                                    PendingIntent.getActivity(mService, 0, clickIntent, 0))
+                            .setDeleteIntent(
+                                    PendingIntent.getBroadcast(mService, 0, deleteIntent, 0))
+                            .setLocalOnly(true)
+                            .build();
+            nm.notify(mNotificationId, notification);
+        }
+
+        private void removePbapNotification(int id) {
+            NotificationManager nm =
+                    (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
+            nm.cancel(id);
+        }
+
+        private synchronized void notifyAuthCancelled() {
+            mObexAuth.setCancelled(true);
+        }
+
+        private synchronized void notifyAuthKeyInput(final String key) {
+            if (key != null) {
+                mObexAuth.setSessionKey(key);
+            }
+            mObexAuth.setChallenged(true);
+        }
+    }
+
+    /**
+     * Get the current connection state of this state machine
+     *
+     * @return current connection state, one of {@link BluetoothProfile#STATE_DISCONNECTED},
+     * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
+     * {@link BluetoothProfile#STATE_DISCONNECTING}
+     */
+    synchronized int getConnectionState() {
+        PbapStateBase state = (PbapStateBase) getCurrentState();
+        if (state == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return state.getConnectionStateInt();
+    }
+
+    @Override
+    protected void log(String msg) {
+        if (DEBUG) {
+            super.log(msg);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/pbapclient/Authenticator.java b/src/com/android/bluetooth/pbapclient/Authenticator.java
index 4bca7c5..0811499 100644
--- a/src/com/android/bluetooth/pbapclient/Authenticator.java
+++ b/src/com/android/bluetooth/pbapclient/Authenticator.java
@@ -49,8 +49,8 @@
 
     // Ignore attempts to confirm credentials
     @Override
-    public Bundle confirmCredentials(AccountAuthenticatorResponse r, Account account,
-            Bundle bundle) throws NetworkErrorException {
+    public Bundle confirmCredentials(AccountAuthenticatorResponse r, Account account, Bundle bundle)
+            throws NetworkErrorException {
         Log.d(TAG, "got call", new Exception());
         return null;
     }
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
index f01ecf4..ab8091a 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexAuthenticator.java
@@ -30,14 +30,14 @@
 
 class BluetoothPbapObexAuthenticator implements Authenticator {
 
-    private final static String TAG = "BluetoothPbapObexAuthenticator";
+    private static final String TAG = "BluetoothPbapObexAuthenticator";
 
     //Default session key for legacy devices is 0000
     private String mSessionKey = "0000";
 
     private final Handler mCallback;
 
-    public BluetoothPbapObexAuthenticator(Handler callback) {
+    BluetoothPbapObexAuthenticator(Handler callback) {
         mCallback = callback;
     }
 
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexTransport.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexTransport.java
index 8c2ff80..4842b3d 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapObexTransport.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapObexTransport.java
@@ -30,7 +30,7 @@
 
     private BluetoothSocket mSocket = null;
 
-    public BluetoothPbapObexTransport(BluetoothSocket rfs) {
+    BluetoothPbapObexTransport(BluetoothSocket rfs) {
         super();
         mSocket = rfs;
     }
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
index a0af235..1bd71ce 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequest.java
@@ -49,11 +49,11 @@
 
     private ClientOperation mOp = null;
 
-    public BluetoothPbapRequest() {
+    BluetoothPbapRequest() {
         mHeaderSet = new HeaderSet();
     }
 
-    final public boolean isSuccess() {
+    public final boolean isSuccess() {
         return (mResponseCode == ResponseCodes.OBEX_HTTP_OK);
     }
 
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
index 88b9e2e..ddbf215 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java
@@ -20,7 +20,6 @@
 import android.util.Log;
 
 import com.android.vcard.VCardEntry;
-import com.android.bluetooth.pbapclient.ObexAppParameters;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -44,8 +43,7 @@
 
     private final byte mFormat;
 
-    public BluetoothPbapRequestPullPhoneBook(
-            String pbName, Account account, long filter, byte format,
+    BluetoothPbapRequestPullPhoneBook(String pbName, Account account, long filter, byte format,
             int maxListCount, int listStartOffset) {
         mAccount = account;
         if (maxListCount < 0 || maxListCount > 65535) {
diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardList.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardList.java
index 380a655..f6be8da 100644
--- a/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardList.java
+++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapVcardList.java
@@ -52,7 +52,7 @@
         }
     }
 
-    public BluetoothPbapVcardList(Account account, InputStream in, byte format) throws IOException {
+    BluetoothPbapVcardList(Account account, InputStream in, byte format) throws IOException {
         mAccount = account;
         parse(in, format);
     }
@@ -67,7 +67,7 @@
         }
 
         VCardEntryConstructor constructor =
-            new VCardEntryConstructor(VCardConfig.VCARD_TYPE_V21_GENERIC, mAccount);
+                new VCardEntryConstructor(VCardConfig.VCARD_TYPE_V21_GENERIC, mAccount);
         VCardEntryCounter counter = new VCardEntryCounter();
         CardEntryHandler handler = new CardEntryHandler();
 
diff --git a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
index 132d010..77665a5 100644
--- a/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
+++ b/src/com/android/bluetooth/pbapclient/CallLogPullRequest.java
@@ -15,12 +15,17 @@
  */
 package com.android.bluetooth.pbapclient;
 
+import android.accounts.Account;
 import android.content.ContentProviderOperation;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
 import android.os.RemoteException;
 import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
 import android.util.Log;
 import android.util.Pair;
 
@@ -30,21 +35,26 @@
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 
 public class CallLogPullRequest extends PullRequest {
-    private static boolean DBG = true;
-    private static boolean VDBG = false;
-    private static String TAG = "PbapCallLogPullRequest";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+    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";
 
+    private final Account mAccount;
     private Context mContext;
+    private HashMap<String, Integer> mCallCounter;
 
-    public CallLogPullRequest(Context context, String path) {
+    public CallLogPullRequest(Context context, String path, HashMap<String, Integer> map,
+            Account account) {
         mContext = context;
         this.path = path;
+        mCallCounter = map;
+        mAccount = account;
     }
 
     @Override
@@ -78,14 +88,20 @@
                 ContentValues values = new ContentValues();
 
                 values.put(CallLog.Calls.TYPE, type);
-
+                values.put(Calls.PHONE_ACCOUNT_ID, mAccount.hashCode());
                 List<PhoneData> phones = vcard.getPhoneList();
                 if (phones == null || phones.get(0).getNumber().equals(";")) {
                     values.put(CallLog.Calls.NUMBER, "");
                 } else {
-                    values.put(CallLog.Calls.NUMBER, phones.get(0).getNumber());
+                    String phoneNumber = phones.get(0).getNumber();
+                    values.put(CallLog.Calls.NUMBER, phoneNumber);
+                    if (mCallCounter.get(phoneNumber) != null) {
+                        int updateCounter = mCallCounter.get(phoneNumber) + 1;
+                        mCallCounter.put(phoneNumber, updateCounter);
+                    } else {
+                        mCallCounter.put(phoneNumber, 1);
+                    }
                 }
-
                 List<Pair<String, String>> irmc = vcard.getUnknownXData();
                 SimpleDateFormat parser = new SimpleDateFormat(TIMESTAMP_FORMAT);
                 if (irmc != null) {
@@ -102,12 +118,17 @@
                         }
                     }
                 }
-
                 ops.add(ContentProviderOperation.newInsert(CallLog.Calls.CONTENT_URI)
-                        .withValues(values).withYieldAllowed(true).build());
+                        .withValues(values)
+                        .withYieldAllowed(true)
+                        .build());
             }
             mContext.getContentResolver().applyBatch(CallLog.AUTHORITY, ops);
             Log.d(TAG, "Updated call logs.");
+            //OUTGOING_TYPE is the last callLog we fetched.
+            if (type == CallLog.Calls.OUTGOING_TYPE) {
+                updateTimesContacted();
+            }
         } catch (RemoteException | OperationApplicationException e) {
             Log.d(TAG, "Failed to update call log for path=" + path, e);
         } finally {
@@ -116,4 +137,29 @@
             }
         }
     }
+
+    private void updateTimesContacted() {
+        for (String key : mCallCounter.keySet()) {
+            ContentValues values = new ContentValues();
+            values.put(ContactsContract.RawContacts.TIMES_CONTACTED, mCallCounter.get(key));
+            Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode(key));
+            Cursor c = mContext.getContentResolver().query(uri, null, null, null);
+            if (c != null && c.getCount() > 0) {
+                c.moveToNext();
+                String contactId = c.getString(c.getColumnIndex(
+                        ContactsContract.PhoneLookup.CONTACT_ID));
+                if (VDBG) {
+                    Log.d(TAG, "onPullComplete: ID " + contactId + " key : " + key);
+                }
+                String where = ContactsContract.RawContacts.CONTACT_ID + "=" + contactId;
+                mContext.getContentResolver().update(
+                        ContactsContract.RawContacts.CONTENT_URI, values, where, null);
+            }
+        }
+        if (DBG) {
+            Log.d(TAG, "Updated TIMES_CONTACTED");
+        }
+    }
+
 }
diff --git a/src/com/android/bluetooth/pbapclient/ObexAppParameters.java b/src/com/android/bluetooth/pbapclient/ObexAppParameters.java
index 35cb468..05e750f 100644
--- a/src/com/android/bluetooth/pbapclient/ObexAppParameters.java
+++ b/src/com/android/bluetooth/pbapclient/ObexAppParameters.java
@@ -35,7 +35,7 @@
         mParams = new HashMap<Byte, byte[]>();
 
         if (raw != null) {
-            for (int i = 0; i < raw.length;) {
+            for (int i = 0; i < raw.length; ) {
                 if (raw.length - i < 2) {
                     break;
                 }
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index cbea8c5..913ba0e 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -27,12 +27,14 @@
 import android.os.Looper;
 import android.os.Message;
 import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothObexTransport;
 import com.android.bluetooth.R;
 
 import java.io.IOException;
+import java.util.HashMap;
 
 import javax.obex.ClientSession;
 import javax.obex.HeaderSet;
@@ -53,8 +55,22 @@
     // The following constants are pulled from the Bluetooth Phone Book Access Profile specification
     // 1.1
     private static final byte[] PBAP_TARGET = new byte[]{
-            0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66,
-            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
+            0x79,
+            0x61,
+            0x35,
+            (byte) 0xf0,
+            (byte) 0xf0,
+            (byte) 0xc5,
+            0x11,
+            (byte) 0xd8,
+            0x09,
+            0x66,
+            0x08,
+            0x00,
+            0x20,
+            0x0c,
+            (byte) 0x9a,
+            0x66
     };
 
     private static final int PBAP_FEATURE_DEFAULT_IMAGE_FORMAT = 0x00000200;
@@ -72,9 +88,9 @@
 
     private static final int PBAP_SUPPORTED_FEATURE =
             PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_BROWSING | PBAP_FEATURE_DOWNLOADING;
-    private static final long PBAP_REQUESTED_FIELDS = PBAP_FILTER_VERSION | PBAP_FILTER_FN
-            | PBAP_FILTER_N | PBAP_FILTER_PHOTO | PBAP_FILTER_ADR | PBAP_FILTER_TEL
-            | PBAP_FILTER_NICKNAME;
+    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;
 
@@ -82,6 +98,7 @@
     public static final String MCH_PATH = "telecom/mch.vcf";
     public static final String ICH_PATH = "telecom/ich.vcf";
     public static final String OCH_PATH = "telecom/och.vcf";
+
     public static final byte VCARD_TYPE_21 = 0;
     public static final byte VCARD_TYPE_30 = 1;
 
@@ -107,8 +124,8 @@
         mPbapClientStateMachine = stateMachine;
         mAuth = new BluetoothPbapObexAuthenticator(this);
         mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext());
-        mAccount = new Account(mDevice.getAddress(), mContext.getString(
-                R.string.pbap_account_type));
+        mAccount =
+                new Account(mDevice.getAddress(), mContext.getString(R.string.pbap_account_type));
     }
 
     /**
@@ -117,41 +134,41 @@
      * @param Builder To build  BluetoothPbapClientHandler Instance.
      */
     PbapClientConnectionHandler(Builder pceHandlerbuild) {
-        super(pceHandlerbuild.looper);
+        super(pceHandlerbuild.mLooper);
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        mDevice = pceHandlerbuild.device;
-        mContext = pceHandlerbuild.context;
-        mPbapClientStateMachine = pceHandlerbuild.clientStateMachine;
+        mDevice = pceHandlerbuild.mDevice;
+        mContext = pceHandlerbuild.mContext;
+        mPbapClientStateMachine = pceHandlerbuild.mClientStateMachine;
         mAuth = new BluetoothPbapObexAuthenticator(this);
         mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext());
-        mAccount = new Account(mDevice.getAddress(), mContext.getString(
-                R.string.pbap_account_type));
+        mAccount =
+                new Account(mDevice.getAddress(), mContext.getString(R.string.pbap_account_type));
     }
 
     public static class Builder {
 
-        private Looper looper;
-        private Context context;
-        private BluetoothDevice device;
-        private PbapClientStateMachine clientStateMachine;
+        private Looper mLooper;
+        private Context mContext;
+        private BluetoothDevice mDevice;
+        private PbapClientStateMachine mClientStateMachine;
 
         public Builder setLooper(Looper loop) {
-            this.looper = loop;
+            this.mLooper = loop;
             return this;
         }
 
         public Builder setClientSM(PbapClientStateMachine clientStateMachine) {
-            this.clientStateMachine = clientStateMachine;
+            this.mClientStateMachine = clientStateMachine;
             return this;
         }
 
         public Builder setRemoteDevice(BluetoothDevice device) {
-            this.device = device;
+            this.mDevice = device;
             return this;
         }
 
         public Builder setContext(Context context) {
-            this.context = context;
+            this.mContext = context;
             return this;
         }
 
@@ -164,13 +181,17 @@
 
     @Override
     public void handleMessage(Message msg) {
-        if (DBG) Log.d(TAG, "Handling Message = " + msg.what);
+        if (DBG) {
+            Log.d(TAG, "Handling Message = " + msg.what);
+        }
         switch (msg.what) {
             case MSG_CONNECT:
                 mPseRec = (SdpPseRecord) msg.obj;
                 /* To establish a connection, first open a socket and then create an OBEX session */
                 if (connectSocket()) {
-                    if (DBG) Log.d(TAG, "Socket connected");
+                    if (DBG) {
+                        Log.d(TAG, "Socket connected");
+                    }
                 } else {
                     Log.w(TAG, "Socket CONNECT Failure ");
                     mPbapClientStateMachine.obtainMessage(
@@ -188,48 +209,56 @@
                 break;
 
             case MSG_DISCONNECT:
-                if (DBG) Log.d(TAG, "Starting Disconnect");
+                if (DBG) {
+                    Log.d(TAG, "Starting Disconnect");
+                }
                 try {
                     if (mObexSession != null) {
-                        if (DBG) Log.d(TAG, "obexSessionDisconnect" + mObexSession);
+                        if (DBG) {
+                            Log.d(TAG, "obexSessionDisconnect" + mObexSession);
+                        }
                         mObexSession.disconnect(null);
                         mObexSession.close();
                     }
 
-                    if (DBG) Log.d(TAG, "Closing Socket");
+                    if (DBG) {
+                        Log.d(TAG, "Closing Socket");
+                    }
                     closeSocket();
                 } catch (IOException e) {
                     Log.w(TAG, "DISCONNECT Failure ", e);
                 }
-                if (DBG) Log.d(TAG, "Completing Disconnect");
+                if (DBG) {
+                    Log.d(TAG, "Completing Disconnect");
+                }
                 removeAccount(mAccount);
-                mContext.getContentResolver()
-                        .delete(CallLog.Calls.CONTENT_URI, null, null);
-                mPbapClientStateMachine.obtainMessage(
-                        PbapClientStateMachine.MSG_CONNECTION_CLOSED).sendToTarget();
+                removeCallLog(mAccount);
+
+                mPbapClientStateMachine.obtainMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED)
+                    .sendToTarget();
                 break;
 
             case MSG_DOWNLOAD:
                 try {
                     mAccountCreated = addAccount(mAccount);
-                    if (mAccountCreated == false) {
+                    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);
+                            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();
-
-                    downloadCallLog(MCH_PATH);
-                    downloadCallLog(ICH_PATH);
-                    downloadCallLog(OCH_PATH);
+                    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());
                 }
@@ -249,8 +278,8 @@
             if (mPseRec == null) {
                 // BackWardCompatability: Fall back to create RFCOMM through UUID.
                 Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid());
-                mSocket = mDevice.createRfcommSocketToServiceRecord(
-                        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());
                 mSocket = mDevice.createL2capSocket(mPseRec.getL2capPsm());
@@ -277,7 +306,9 @@
         boolean connectionSuccessful = false;
 
         try {
-            if (DBG) Log.v(TAG, "Start Obex Client Session");
+            if (DBG) {
+                Log.v(TAG, "Start Obex Client Session");
+            }
             BluetoothObexTransport transport = new BluetoothObexTransport(mSocket);
             mObexSession = new ClientSession(transport);
             mObexSession.setAuthenticator(mAuth);
@@ -287,8 +318,7 @@
 
             if (mPseRec != null) {
                 if (DBG) {
-                    Log.d(TAG, "Remote PbapSupportedFeatures "
-                            + mPseRec.getSupportedFeatures());
+                    Log.d(TAG, "Remote PbapSupportedFeatures " + mPseRec.getSupportedFeatures());
                 }
 
                 ObexAppParameters oap = new ObexAppParameters();
@@ -302,9 +332,11 @@
             }
             HeaderSet connectionResponse = mObexSession.connect(connectionRequest);
 
-            connectionSuccessful = (connectionResponse.getResponseCode() ==
-                    ResponseCodes.OBEX_HTTP_OK);
-            if (DBG) Log.d(TAG, "Success = " + Boolean.toString(connectionSuccessful));
+            connectionSuccessful =
+                    (connectionResponse.getResponseCode() == ResponseCodes.OBEX_HTTP_OK);
+            if (DBG) {
+                Log.d(TAG, "Success = " + Boolean.toString(connectionSuccessful));
+            }
         } catch (IOException e) {
             Log.w(TAG, "CONNECT Failure " + e.toString());
             closeSocket();
@@ -322,7 +354,9 @@
     private void closeSocket() {
         try {
             if (mSocket != null) {
-                if (DBG) Log.d(TAG, "Closing socket" + mSocket);
+                if (DBG) {
+                    Log.d(TAG, "Closing socket" + mSocket);
+                }
                 mSocket.close();
                 mSocket = null;
             }
@@ -332,13 +366,14 @@
         }
     }
 
-    void downloadCallLog(String path) {
+    void downloadCallLog(String path, HashMap<String, Integer> callCounter) {
         try {
             BluetoothPbapRequestPullPhoneBook request =
                     new BluetoothPbapRequestPullPhoneBook(path, mAccount, 0, VCARD_TYPE_30, 0, 0);
             request.execute(mObexSession);
             CallLogPullRequest processor =
-                    new CallLogPullRequest(mPbapClientStateMachine.getContext(), path);
+                    new CallLogPullRequest(mPbapClientStateMachine.getContext(), path,
+                        callCounter, mAccount);
             processor.setResults(request.getList());
             processor.onPullComplete();
         } catch (IOException e) {
@@ -356,13 +391,29 @@
         return false;
     }
 
-    private void removeAccount(Account acc) {
-        if (mAccountManager.removeAccountExplicitly(acc)) {
+    private void removeAccount(Account account) {
+        if (mAccountManager.removeAccountExplicitly(account)) {
             if (DBG) {
-                Log.d(TAG, "Removed account " + acc);
+                Log.d(TAG, "Removed account " + account);
             }
         } else {
             Log.e(TAG, "Failed to remove account " + mAccount);
         }
     }
+
+    private void removeCallLog(Account account) {
+        try {
+            // need to check call table is exist ?
+            if (mContext.getContentResolver() == null) {
+                if (DBG) {
+                    Log.d(TAG, "CallLog ContentResolver is not found");
+                }
+                return;
+            }
+            String where = Calls.PHONE_ACCOUNT_ID + "=" + account.hashCode();
+            mContext.getContentResolver().delete(CallLog.Calls.CONTENT_URI, where, null);
+        } 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 9b92c23..0a9541c 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -29,15 +29,14 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
 
-import java.lang.IllegalArgumentException;
 import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Provides Bluetooth Phone Book Access Profile Client profile.
@@ -55,18 +54,15 @@
     private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver();
 
     @Override
-    protected String getName() {
-        return TAG;
-    }
-
-    @Override
     public IProfileServiceBinder initBinder() {
         return new BluetoothPbapClientBinder(this);
     }
 
     @Override
     protected boolean start() {
-        if (DBG) Log.d(TAG, "onStart");
+        if (DBG) {
+            Log.d(TAG, "onStart");
+        }
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         // delay initial download until after the user is unlocked to add an account.
@@ -74,7 +70,7 @@
         try {
             registerReceiver(mPbapBroadcastReceiver, filter);
         } catch (Exception e) {
-            Log.w(TAG,"Unable to register pbapclient receiver", e);
+            Log.w(TAG, "Unable to register pbapclient receiver", e);
         }
         removeUncleanAccounts();
         setPbapClientService(this);
@@ -86,7 +82,7 @@
         try {
             unregisterReceiver(mPbapBroadcastReceiver);
         } catch (Exception e) {
-            Log.w(TAG,"Unable to unregister pbapclient receiver", e);
+            Log.w(TAG, "Unable to unregister pbapclient receiver", e);
         }
         for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) {
             pbapClientStateMachine.doQuit();
@@ -95,10 +91,10 @@
     }
 
     @Override
-    protected boolean cleanup() {
+    protected void cleanup() {
         removeUncleanAccounts();
-        clearPbapClientService();
-        return true;
+        // TODO: Should move to stop()
+        setPbapClientService(null);
     }
 
     void cleanupDevice(BluetoothDevice device) {
@@ -139,7 +135,7 @@
                 if (getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) {
                     disconnect(device);
                 }
-            } else if(action.equals(Intent.ACTION_USER_UNLOCKED)) {
+            } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
                 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
                     stateMachine.resumeDownload();
                 }
@@ -154,14 +150,13 @@
             implements IProfileServiceBinder {
         private PbapClientService mService;
 
-        public BluetoothPbapClientBinder(PbapClientService svc) {
+        BluetoothPbapClientBinder(PbapClientService svc) {
             mService = svc;
         }
 
         @Override
-        public boolean cleanup() {
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
         private PbapClientService getService() {
@@ -179,7 +174,9 @@
         @Override
         public boolean connect(BluetoothDevice device) {
             PbapClientService service = getService();
-            if (DBG) Log.d(TAG, "PbapClient Binder connect " );
+            if (DBG) {
+                Log.d(TAG, "PbapClient Binder connect ");
+            }
             if (service == null) {
                 Log.e(TAG, "PbapClient Binder connect no service");
                 return false;
@@ -204,6 +201,7 @@
             }
             return service.getConnectedDevices();
         }
+
         @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
             PbapClientService service = getService();
@@ -245,47 +243,30 @@
 
     // API methods
     public static synchronized PbapClientService getPbapClientService() {
-        if (sPbapClientService != null && sPbapClientService.isAvailable()) {
-            if (DBG) {
-                Log.d(TAG, "getPbapClientService(): returning " + sPbapClientService);
-            }
-            return sPbapClientService;
+        if (sPbapClientService == null) {
+            Log.w(TAG, "getPbapClientService(): service is null");
+            return null;
         }
-        if (DBG) {
-            if (sPbapClientService == null) {
-                Log.d(TAG, "getPbapClientService(): service is NULL");
-            } else if (!(sPbapClientService.isAvailable())) {
-                Log.d(TAG, "getPbapClientService(): service is not available");
-            }
+        if (!sPbapClientService.isAvailable()) {
+            Log.w(TAG, "getPbapClientService(): service is not available");
+            return null;
         }
-        return null;
+        return sPbapClientService;
     }
 
     private static synchronized void setPbapClientService(PbapClientService instance) {
-        if (instance != null && instance.isAvailable()) {
-            if (DBG) {
-                Log.d(TAG, "setPbapClientService(): previously set to: " + sPbapClientService);
-            }
-            sPbapClientService = instance;
-        } else {
-            if (DBG) {
-                if (sPbapClientService == null) {
-                    Log.d(TAG, "setPbapClientService(): service not available");
-                } else if (!sPbapClientService.isAvailable()) {
-                    Log.d(TAG, "setPbapClientService(): service is cleaning up");
-                }
-            }
+        if (DBG) {
+            Log.d(TAG, "setPbapClientService(): set to: " + instance);
         }
-    }
-
-    private static synchronized void clearPbapClientService() {
-        sPbapClientService = null;
+        sPbapClientService = instance;
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (device == null) throw new IllegalArgumentException("Null device");
+        if (device == null) {
+            throw new IllegalArgumentException("Null device");
+        }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-        Log.d(TAG,"Received request to ConnectPBAPPhonebook " + device.getAddress());
+        Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
         if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
             return false;
         }
@@ -305,7 +286,9 @@
     }
 
     boolean disconnect(BluetoothDevice device) {
-        if (device == null) throw new IllegalArgumentException("Null device");
+        if (device == null) {
+            throw new IllegalArgumentException("Null device");
+        }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
         if (pbapClientStateMachine != null) {
@@ -328,7 +311,8 @@
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0);
         for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry :
-                mPbapClientStateMachineMap.entrySet()) {
+                mPbapClientStateMachineMap
+                .entrySet()) {
             int currentDeviceState = stateMachineEntry.getValue().getConnectionState();
             for (int state : states) {
                 if (currentDeviceState == state) {
@@ -341,7 +325,9 @@
     }
 
     int getConnectionState(BluetoothDevice device) {
-        if (device == null) throw new IllegalArgumentException("Null device");
+        if (device == null) {
+            throw new IllegalArgumentException("Null device");
+        }
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
         if (pbapClientStateMachine == null) {
@@ -352,19 +338,22 @@
     }
 
     public boolean setPriority(BluetoothDevice device, int priority) {
-        if (device == null) throw new IllegalArgumentException("Null device");
+        if (device == null) {
+            throw new IllegalArgumentException("Null device");
+        }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Settings.Global.putInt(getContentResolver(),
-                Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
-                priority);
+                Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()), priority);
         if (DBG) {
-            Log.d(TAG,"Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
-        if (device == null) throw new IllegalArgumentException("Null device");
+        if (device == null) {
+            throw new IllegalArgumentException("Null device");
+        }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         int priority = Settings.Global.getInt(getContentResolver(),
                 Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
index cc2f9d9..3cc9094 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
@@ -42,8 +42,8 @@
 package com.android.bluetooth.pbapclient;
 
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothPbapClient;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -56,8 +56,9 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
-import com.android.bluetooth.R;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -138,7 +139,9 @@
 
         @Override
         public void enter() {
-            if (DBG) Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
+            if (DBG) {
+                Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
+            }
             onConnectionStateChanged(mCurrentDevice, mMostRecentState,
                     BluetoothProfile.STATE_CONNECTING);
             mSdpReceiver = new SDPBroadcastReceiver();
@@ -148,26 +151,28 @@
 
             // Create a separate handler instance and thread for performing
             // connect/download/disconnect operations as they may be time consuming and error prone.
-            mHandlerThread = new HandlerThread("PBAP PCE handler",
-                    Process.THREAD_PRIORITY_BACKGROUND);
+            mHandlerThread =
+                    new HandlerThread("PBAP PCE handler", Process.THREAD_PRIORITY_BACKGROUND);
             mHandlerThread.start();
-            mConnectionHandler = new PbapClientConnectionHandler.Builder()
-                                         .setLooper(mHandlerThread.getLooper())
-                                         .setContext(mService)
-                                         .setClientSM(PbapClientStateMachine.this)
-                                         .setRemoteDevice(mCurrentDevice)
-                                         .build();
+            mConnectionHandler =
+                    new PbapClientConnectionHandler.Builder().setLooper(mHandlerThread.getLooper())
+                            .setContext(mService)
+                            .setClientSM(PbapClientStateMachine.this)
+                            .setRemoteDevice(mCurrentDevice)
+                            .build();
 
             sendMessageDelayed(MSG_CONNECT_TIMEOUT, CONNECT_TIMEOUT);
         }
 
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) Log.d(TAG, "Processing MSG " + message.what + " from " + this.getName());
+            if (DBG) {
+                Log.d(TAG, "Processing MSG " + message.what + " from " + this.getName());
+            }
             switch (message.what) {
                 case MSG_DISCONNECT:
-                    if (message.obj instanceof BluetoothDevice
-                            && message.obj.equals(mCurrentDevice)) {
+                    if (message.obj instanceof BluetoothDevice && message.obj.equals(
+                            mCurrentDevice)) {
                         removeMessages(MSG_CONNECT_TIMEOUT);
                         transitionTo(mDisconnecting);
                     }
@@ -206,21 +211,26 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 String action = intent.getAction();
-                if (DBG) Log.v(TAG, "onReceive" + action);
+                if (DBG) {
+                    Log.v(TAG, "onReceive" + action);
+                }
                 if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
-                    BluetoothDevice device = intent.getParcelableExtra(
-                            BluetoothDevice.EXTRA_DEVICE);
+                    BluetoothDevice device =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                     if (!device.equals(getDevice())) {
                         Log.w(TAG, "SDP Record fetched for different device - Ignore");
                         return;
                     }
                     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 (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)) {
-                        sendMessage(MSG_SDP_COMPLETE, intent
-                                .getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD));
+                        sendMessage(MSG_SDP_COMPLETE,
+                                intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD));
                     }
                 }
             }
@@ -251,7 +261,9 @@
 
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) Log.d(TAG, "Processing MSG " + message.what + " from " + this.getName());
+            if (DBG) {
+                Log.d(TAG, "Processing MSG " + message.what + " from " + this.getName());
+            }
             switch (message.what) {
                 case MSG_CONNECTION_CLOSED:
                     removeMessages(MSG_DISCONNECT_TIMEOUT);
@@ -295,11 +307,13 @@
 
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) Log.d(TAG, "Processing MSG " + message.what + " from " + this.getName());
+            if (DBG) {
+                Log.d(TAG, "Processing MSG " + message.what + " from " + this.getName());
+            }
             switch (message.what) {
                 case MSG_DISCONNECT:
-                    if ((message.obj instanceof BluetoothDevice) &&
-                            ((BluetoothDevice) message.obj).equals(mCurrentDevice)) {
+                    if ((message.obj instanceof BluetoothDevice)
+                            && ((BluetoothDevice) message.obj).equals(mCurrentDevice)) {
                         transitionTo(mDisconnecting);
                     }
                     break;
@@ -322,6 +336,9 @@
             Log.w(TAG, "onConnectionStateChanged with invalid device");
             return;
         }
+        if (prevState != state && state == BluetoothProfile.STATE_CONNECTED) {
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.PBAP_CLIENT);
+        }
         Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + state);
         Intent intent = new Intent(BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
diff --git a/src/com/android/bluetooth/pbapclient/PhonebookEntry.java b/src/com/android/bluetooth/pbapclient/PhonebookEntry.java
index 4d38f35..62093e5 100644
--- a/src/com/android/bluetooth/pbapclient/PhonebookEntry.java
+++ b/src/com/android/bluetooth/pbapclient/PhonebookEntry.java
@@ -18,8 +18,8 @@
 import com.android.vcard.VCardEntry;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 
 /**
  *  A simpler more public version of VCardEntry.
@@ -41,11 +41,12 @@
             }
 
             Name n = ((Name) o);
-            return (family == n.family || family != null && family.equals(n.family)) &&
-                    (given == n.given ||  given != null && given.equals(n.given)) &&
-                    (middle == n.middle || middle != null && middle.equals(n.middle)) &&
-                    (prefix == n.prefix || prefix != null && prefix.equals(n.prefix)) &&
-                    (suffix == n.suffix || suffix != null && suffix.equals(n.suffix));
+            return (Objects.equals(family, n.family) || family != null && family.equals(n.family))
+                    && (Objects.equals(given, n.given) || given != null && given.equals(n.given))
+                    && (Objects.equals(middle, n.middle) || middle != null && middle.equals(
+                    n.middle)) && (Objects.equals(prefix, n.prefix)
+                    || prefix != null && prefix.equals(n.prefix)) && (
+                    Objects.equals(suffix, n.suffix) || suffix != null && suffix.equals(n.suffix));
         }
 
         @Override
@@ -87,7 +88,7 @@
             }
 
             Phone p = (Phone) o;
-            return (number == p.number || number != null && number.equals(p.number))
+            return (Objects.equals(number, p.number) || number != null && number.equals(p.number))
                     && type == p.type;
         }
 
diff --git a/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java b/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
index 8881baf..49fbd44 100644
--- a/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
+++ b/src/com/android/bluetooth/pbapclient/PhonebookPullRequest.java
@@ -15,32 +15,18 @@
  */
 package com.android.bluetooth.pbapclient;
 
-import com.android.vcard.VCardEntry;
-
 import android.accounts.Account;
 import android.content.ContentProviderOperation;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.OperationApplicationException;
-import android.provider.ContactsContract;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
 import android.os.RemoteException;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.RawContactsEntity;
-import android.provider.ContactsContract.Contacts.Entity;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract;
 import android.util.Log;
 
 import com.android.vcard.VCardEntry;
 
-import java.lang.InterruptedException;
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
 
 public class PhonebookPullRequest extends PullRequest {
     private static final int MAX_OPS = 250;
diff --git a/src/com/android/bluetooth/pbapclient/PullRequest.java b/src/com/android/bluetooth/pbapclient/PullRequest.java
index 4944f85..ce5a7a0 100644
--- a/src/com/android/bluetooth/pbapclient/PullRequest.java
+++ b/src/com/android/bluetooth/pbapclient/PullRequest.java
@@ -22,6 +22,7 @@
 public abstract class PullRequest {
     public String path;
     protected List<VCardEntry> mEntries;
+
     public abstract void onPullComplete();
 
     @Override
diff --git a/src/com/android/bluetooth/sap/SapMessage.java b/src/com/android/bluetooth/sap/SapMessage.java
index a2fcebb..147a654 100644
--- a/src/com/android/bluetooth/sap/SapMessage.java
+++ b/src/com/android/bluetooth/sap/SapMessage.java
@@ -3,6 +3,24 @@
 import android.hardware.radio.V1_0.ISap;
 import android.hardware.radio.V1_0.SapApduType;
 import android.hardware.radio.V1_0.SapTransferProtocol;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.google.protobuf.micro.CodedOutputStreamMicro;
+import com.google.protobuf.micro.InvalidProtocolBufferMicroException;
+
+import org.android.btsap.SapApi;
+import org.android.btsap.SapApi.MsgHeader;
+import org.android.btsap.SapApi.RIL_SIM_SAP_APDU_RSP;
+import org.android.btsap.SapApi.RIL_SIM_SAP_CONNECT_RSP;
+import org.android.btsap.SapApi.RIL_SIM_SAP_DISCONNECT_IND;
+import org.android.btsap.SapApi.RIL_SIM_SAP_POWER_RSP;
+import org.android.btsap.SapApi.RIL_SIM_SAP_RESET_SIM_RSP;
+import org.android.btsap.SapApi.RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP;
+import org.android.btsap.SapApi.RIL_SIM_SAP_STATUS_IND;
+import org.android.btsap.SapApi.RIL_SIM_SAP_TRANSFER_ATR_RSP;
+import org.android.btsap.SapApi.RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -11,13 +29,6 @@
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.android.btsap.SapApi;
-import org.android.btsap.SapApi.*;
-import com.google.protobuf.micro.*;
-
-import android.os.RemoteException;
-import android.util.Log;
-
 /**
  * SapMessage is used for incoming and outgoing messages.
  *
@@ -32,122 +43,122 @@
     public static final boolean TEST = false;
 
     /* Message IDs - SAP specification */
-    public static final int ID_CONNECT_REQ        = 0x00;
-    public static final int ID_CONNECT_RESP       = 0x01;
+    public static final int ID_CONNECT_REQ = 0x00;
+    public static final int ID_CONNECT_RESP = 0x01;
 
-    public static final int ID_DISCONNECT_REQ     = 0x02;
-    public static final int ID_DISCONNECT_RESP    = 0x03;
-    public static final int ID_DISCONNECT_IND     = 0x04;
+    public static final int ID_DISCONNECT_REQ = 0x02;
+    public static final int ID_DISCONNECT_RESP = 0x03;
+    public static final int ID_DISCONNECT_IND = 0x04;
 
-    public static final int ID_TRANSFER_APDU_REQ  = 0x05;
+    public static final int ID_TRANSFER_APDU_REQ = 0x05;
     public static final int ID_TRANSFER_APDU_RESP = 0x06;
 
-    public static final int ID_TRANSFER_ATR_REQ   = 0x07;
-    public static final int ID_TRANSFER_ATR_RESP  = 0x08;
+    public static final int ID_TRANSFER_ATR_REQ = 0x07;
+    public static final int ID_TRANSFER_ATR_RESP = 0x08;
 
-    public static final int ID_POWER_SIM_OFF_REQ  = 0x09;
+    public static final int ID_POWER_SIM_OFF_REQ = 0x09;
     public static final int ID_POWER_SIM_OFF_RESP = 0x0A;
 
-    public static final int ID_POWER_SIM_ON_REQ   = 0x0B;
-    public static final int ID_POWER_SIM_ON_RESP  = 0x0C;
+    public static final int ID_POWER_SIM_ON_REQ = 0x0B;
+    public static final int ID_POWER_SIM_ON_RESP = 0x0C;
 
-    public static final int ID_RESET_SIM_REQ      = 0x0D;
-    public static final int ID_RESET_SIM_RESP     = 0x0E;
+    public static final int ID_RESET_SIM_REQ = 0x0D;
+    public static final int ID_RESET_SIM_RESP = 0x0E;
 
-    public static final int ID_TRANSFER_CARD_READER_STATUS_REQ  = 0x0F;
+    public static final int ID_TRANSFER_CARD_READER_STATUS_REQ = 0x0F;
     public static final int ID_TRANSFER_CARD_READER_STATUS_RESP = 0x10;
 
-    public static final int ID_STATUS_IND         = 0x11;
-    public static final int ID_ERROR_RESP         = 0x12;
+    public static final int ID_STATUS_IND = 0x11;
+    public static final int ID_ERROR_RESP = 0x12;
 
-    public static final int ID_SET_TRANSPORT_PROTOCOL_REQ  = 0x13;
+    public static final int ID_SET_TRANSPORT_PROTOCOL_REQ = 0x13;
     public static final int ID_SET_TRANSPORT_PROTOCOL_RESP = 0x14;
 
     /* Message IDs - RIL specific unsolicited */
     // First RIL message id
-    public static final int ID_RIL_BASE                    = 0x100;
+    public static final int ID_RIL_BASE = 0x100;
     // RIL_UNSOL_RIL_CONNECTED
-    public static final int ID_RIL_UNSOL_CONNECTED         = 0x100;
+    public static final int ID_RIL_UNSOL_CONNECTED = 0x100;
     // A disconnect ind from RIL will be converted after handled locally
-    public static final int ID_RIL_UNSOL_DISCONNECT_IND    = 0x102;
+    public static final int ID_RIL_UNSOL_DISCONNECT_IND = 0x102;
     // All others
-    public static final int ID_RIL_UNKNOWN                 = 0x1ff;
+    public static final int ID_RIL_UNKNOWN = 0x1ff;
 
     /* Message IDs - RIL specific solicited */
-    public static final int ID_RIL_GET_SIM_STATUS_REQ      = 0x200; // RIL_REQUEST_GET_SIM_STATUS
+    public static final int ID_RIL_GET_SIM_STATUS_REQ = 0x200; // RIL_REQUEST_GET_SIM_STATUS
     /* Test signals used to set the reference ril in test mode */
-    public static final int ID_RIL_SIM_ACCESS_TEST_REQ     = 0x201; // RIL_REQUEST_SIM_ACCESS_TEST
-    public static final int ID_RIL_SIM_ACCESS_TEST_RESP    = 0x202; /* response for
+    public static final int ID_RIL_SIM_ACCESS_TEST_REQ = 0x201; // RIL_REQUEST_SIM_ACCESS_TEST
+    public static final int ID_RIL_SIM_ACCESS_TEST_RESP = 0x202; /* response for
                                                                     RIL_REQUEST_SIM_ACCESS_TEST */
 
     /* Parameter IDs and lengths */
-    public static final int PARAM_MAX_MSG_SIZE_ID        = 0x00;
-    public static final int PARAM_MAX_MSG_SIZE_LENGTH    = 2;
+    public static final int PARAM_MAX_MSG_SIZE_ID = 0x00;
+    public static final int PARAM_MAX_MSG_SIZE_LENGTH = 2;
 
-    public static final int PARAM_CONNECTION_STATUS_ID   = 0x01;
+    public static final int PARAM_CONNECTION_STATUS_ID = 0x01;
     public static final int PARAM_CONNECTION_STATUS_LENGTH = 1;
 
-    public static final int PARAM_RESULT_CODE_ID         = 0x02;
-    public static final int PARAM_RESULT_CODE_LENGTH     = 1;
+    public static final int PARAM_RESULT_CODE_ID = 0x02;
+    public static final int PARAM_RESULT_CODE_LENGTH = 1;
 
-    public static final int PARAM_DISCONNECT_TYPE_ID     = 0x03;
+    public static final int PARAM_DISCONNECT_TYPE_ID = 0x03;
     public static final int PARAM_DISCONNECT_TYPE_LENGTH = 1;
 
-    public static final int PARAM_COMMAND_APDU_ID        = 0x04;
+    public static final int PARAM_COMMAND_APDU_ID = 0x04;
 
-    public static final int PARAM_COMMAND_APDU7816_ID    = 0x10;
+    public static final int PARAM_COMMAND_APDU7816_ID = 0x10;
 
-    public static final int PARAM_RESPONSE_APDU_ID       = 0x05;
+    public static final int PARAM_RESPONSE_APDU_ID = 0x05;
 
-    public static final int PARAM_ATR_ID                 = 0x06;
+    public static final int PARAM_ATR_ID = 0x06;
 
-    public static final int PARAM_CARD_READER_STATUS_ID  = 0x07;
+    public static final int PARAM_CARD_READER_STATUS_ID = 0x07;
     public static final int PARAM_CARD_READER_STATUS_LENGTH = 1;
 
-    public static final int PARAM_STATUS_CHANGE_ID       = 0x08;
-    public static final int PARAM_STATUS_CHANGE_LENGTH   = 1;
+    public static final int PARAM_STATUS_CHANGE_ID = 0x08;
+    public static final int PARAM_STATUS_CHANGE_LENGTH = 1;
 
-    public static final int PARAM_TRANSPORT_PROTOCOL_ID        = 0x09;
-    public static final int PARAM_TRANSPORT_PROTOCOL_LENGTH    = 1;
+    public static final int PARAM_TRANSPORT_PROTOCOL_ID = 0x09;
+    public static final int PARAM_TRANSPORT_PROTOCOL_LENGTH = 1;
 
     /* Result codes */
-    public static final int RESULT_OK                        = 0x00;
-    public static final int RESULT_ERROR_NO_REASON           = 0x01;
+    public static final int RESULT_OK = 0x00;
+    public static final int RESULT_ERROR_NO_REASON = 0x01;
     public static final int RESULT_ERROR_CARD_NOT_ACCESSIBLE = 0x02;
-    public static final int RESULT_ERROR_CARD_POWERED_OFF    = 0x03;
-    public static final int RESULT_ERROR_CARD_REMOVED        = 0x04;
-    public static final int RESULT_ERROR_CARD_POWERED_ON     = 0x05;
-    public static final int RESULT_ERROR_DATA_NOT_AVAILABLE  = 0x06;
-    public static final int RESULT_ERROR_NOT_SUPPORTED       = 0x07;
+    public static final int RESULT_ERROR_CARD_POWERED_OFF = 0x03;
+    public static final int RESULT_ERROR_CARD_REMOVED = 0x04;
+    public static final int RESULT_ERROR_CARD_POWERED_ON = 0x05;
+    public static final int RESULT_ERROR_DATA_NOT_AVAILABLE = 0x06;
+    public static final int RESULT_ERROR_NOT_SUPPORTED = 0x07;
 
     /* Connection Status codes */
-    public static final int CON_STATUS_OK                             = 0x00;
-    public static final int CON_STATUS_ERROR_CONNECTION               = 0x01;
+    public static final int CON_STATUS_OK = 0x00;
+    public static final int CON_STATUS_ERROR_CONNECTION = 0x01;
     public static final int CON_STATUS_ERROR_MAX_MSG_SIZE_UNSUPPORTED = 0x02;
-    public static final int CON_STATUS_ERROR_MAX_MSG_SIZE_TOO_SMALL   = 0x03;
-    public static final int CON_STATUS_OK_ONGOING_CALL                = 0x04;
+    public static final int CON_STATUS_ERROR_MAX_MSG_SIZE_TOO_SMALL = 0x03;
+    public static final int CON_STATUS_OK_ONGOING_CALL = 0x04;
 
     /* Disconnection type */
-    public static final int DISC_GRACEFULL                = 0x00;
-    public static final int DISC_IMMEDIATE                = 0x01;
-    public static final int DISC_FORCED                   = 0x100; // Used internal only
-    public static final int DISC_RFCOMM                   = 0x101; // Used internal only
+    public static final int DISC_GRACEFULL = 0x00;
+    public static final int DISC_IMMEDIATE = 0x01;
+    public static final int DISC_FORCED = 0x100; // Used internal only
+    public static final int DISC_RFCOMM = 0x101; // Used internal only
 
     /* Status Change */
-    public static final int STATUS_UNKNOWN_ERROR       = 0x00;
-    public static final int STATUS_CARD_RESET          = 0x01;
+    public static final int STATUS_UNKNOWN_ERROR = 0x00;
+    public static final int STATUS_CARD_RESET = 0x01;
     public static final int STATUS_CARD_NOT_ACCESSIBLE = 0x02;
-    public static final int STATUS_CARD_REMOVED        = 0x03;
-    public static final int STATUS_CARD_INSERTED       = 0x04;
-    public static final int STATUS_RECOVERED           = 0x05;
+    public static final int STATUS_CARD_REMOVED = 0x03;
+    public static final int STATUS_CARD_INSERTED = 0x04;
+    public static final int STATUS_RECOVERED = 0x05;
 
     /* Transport Protocol */
-    public static final int TRANS_PROTO_T0           = 0x00;
-    public static final int TRANS_PROTO_T1           = 0x01;
+    public static final int TRANS_PROTO_T0 = 0x00;
+    public static final int TRANS_PROTO_T1 = 0x01;
 
     /* Test Mode */
-    public static final int TEST_MODE_DISABLE        = 0x00;
-    public static final int TEST_MODE_ENABLE         = 0x01;
+    public static final int TEST_MODE_DISABLE = 0x00;
+    public static final int TEST_MODE_ENABLE = 0x01;
 
     /* Used to detect uninitialized values */
     public static final int INVALID_VALUE = -1;
@@ -183,13 +194,13 @@
      * Create a SapMessage
      * @param msgType the SAP message type
      */
-    public SapMessage(int msgType){
+    public SapMessage(int msgType) {
         this.mMsgType = msgType;
     }
 
     private static void resetPendingRilMessages() {
         int numMessages = sOngoingRequests.size();
-        if(numMessages != 0) {
+        if (numMessages != 0) {
             Log.w(TAG, "Clearing message queue with size: " + numMessages);
             sOngoingRequests.clear();
         }
@@ -321,28 +332,39 @@
 
     private int getParamCount() {
         int paramCount = 0;
-        if(mMaxMsgSize != INVALID_VALUE)
+        if (mMaxMsgSize != INVALID_VALUE) {
             paramCount++;
-        if(mConnectionStatus != INVALID_VALUE)
+        }
+        if (mConnectionStatus != INVALID_VALUE) {
             paramCount++;
-        if(mResultCode != INVALID_VALUE)
+        }
+        if (mResultCode != INVALID_VALUE) {
             paramCount++;
-        if(mDisconnectionType != INVALID_VALUE)
+        }
+        if (mDisconnectionType != INVALID_VALUE) {
             paramCount++;
-        if(mCardReaderStatus != INVALID_VALUE)
+        }
+        if (mCardReaderStatus != INVALID_VALUE) {
             paramCount++;
-        if(mStatusChange != INVALID_VALUE)
+        }
+        if (mStatusChange != INVALID_VALUE) {
             paramCount++;
-        if(mTransportProtocol != INVALID_VALUE)
+        }
+        if (mTransportProtocol != INVALID_VALUE) {
             paramCount++;
-        if(mApdu != null)
+        }
+        if (mApdu != null) {
             paramCount++;
-        if(mApdu7816 != null)
+        }
+        if (mApdu7816 != null) {
             paramCount++;
-        if(mApduResp != null)
+        }
+        if (mApduResp != null) {
             paramCount++;
-        if(mAtr != null)
+        }
+        if (mAtr != null) {
             paramCount++;
+        }
         return paramCount;
     }
 
@@ -361,52 +383,56 @@
         try {
             paramCount = is.read();
             skip(is, 2); // Skip the 2 padding bytes
-            if(paramCount > 0) {
-                if(VERBOSE) Log.i(TAG, "Parsing message with paramCount: " + paramCount);
-                if(newMessage.parseParameters(paramCount, is) == false)
+            if (paramCount > 0) {
+                if (VERBOSE) {
+                    Log.i(TAG, "Parsing message with paramCount: " + paramCount);
+                }
+                if (!newMessage.parseParameters(paramCount, is)) {
                     return null;
+                }
             }
         } catch (IOException e) {
             Log.w(TAG, e);
             return null;
         }
-        if(DEBUG) Log.i(TAG, "readMessage() Read message: " + getMsgTypeName(requestType));
+        if (DEBUG) {
+            Log.i(TAG, "readMessage() Read message: " + getMsgTypeName(requestType));
+        }
 
         /* Validate parameters */
-        switch(requestType) {
-        case ID_CONNECT_REQ:
-            if(newMessage.getMaxMsgSize() == INVALID_VALUE) {
-                Log.e(TAG, "Missing MaxMsgSize parameter in CONNECT_REQ");
+        switch (requestType) {
+            case ID_CONNECT_REQ:
+                if (newMessage.getMaxMsgSize() == INVALID_VALUE) {
+                    Log.e(TAG, "Missing MaxMsgSize parameter in CONNECT_REQ");
+                    return null;
+                }
+                break;
+            case ID_TRANSFER_APDU_REQ:
+                if (newMessage.getApdu() == null && newMessage.getApdu7816() == null) {
+                    Log.e(TAG, "Missing Apdu parameter in TRANSFER_APDU_REQ");
+                    return null;
+                }
+                newMessage.setSendToRil(true);
+                break;
+            case ID_SET_TRANSPORT_PROTOCOL_REQ:
+                if (newMessage.getTransportProtocol() == INVALID_VALUE) {
+                    Log.e(TAG, "Missing TransportProtocol parameter in SET_TRANSPORT_PROTOCOL_REQ");
+                    return null;
+                }
+                newMessage.setSendToRil(true);
+                break;
+            case ID_TRANSFER_ATR_REQ:  /* No params */
+            case ID_POWER_SIM_OFF_REQ: /* No params */
+            case ID_POWER_SIM_ON_REQ:  /* No params */
+            case ID_RESET_SIM_REQ:     /* No params */
+            case ID_TRANSFER_CARD_READER_STATUS_REQ: /* No params */
+                newMessage.setSendToRil(true);
+                break;
+            case ID_DISCONNECT_REQ:    /* No params */
+                break;
+            default:
+                Log.e(TAG, "Unknown request type");
                 return null;
-            }
-            break;
-        case ID_TRANSFER_APDU_REQ:
-            if(newMessage.getApdu() == null &&
-                   newMessage.getApdu7816() == null) {
-                Log.e(TAG, "Missing Apdu parameter in TRANSFER_APDU_REQ");
-                return null;
-            }
-            newMessage.setSendToRil(true);
-            break;
-        case ID_SET_TRANSPORT_PROTOCOL_REQ:
-            if(newMessage.getTransportProtocol() == INVALID_VALUE) {
-                Log.e(TAG, "Missing TransportProtocol parameter in SET_TRANSPORT_PROTOCOL_REQ");
-                return null;
-            }
-            newMessage.setSendToRil(true);
-            break;
-        case ID_TRANSFER_ATR_REQ:  /* No params */
-        case ID_POWER_SIM_OFF_REQ: /* No params */
-        case ID_POWER_SIM_ON_REQ:  /* No params */
-        case ID_RESET_SIM_REQ:     /* No params */
-        case ID_TRANSFER_CARD_READER_STATUS_REQ: /* No params */
-            newMessage.setSendToRil(true);
-            break;
-        case ID_DISCONNECT_REQ:    /* No params */
-            break;
-        default:
-            Log.e(TAG, "Unknown request type");
-            return null;
         }
         return newMessage;
     }
@@ -422,11 +448,12 @@
         int bytesRead = 0;
         int tmpBytesRead;
         while (bytesRead < bytesToRead) {
-            tmpBytesRead = is.read(buffer, bytesRead, bytesToRead-bytesRead);
-            if(tmpBytesRead == -1)
+            tmpBytesRead = is.read(buffer, bytesRead, bytesToRead - bytesRead);
+            if (tmpBytesRead == -1) {
                 throw new IOException("EOS reached while reading a byte array.");
-            else
+            } else {
                 bytesRead += tmpBytesRead;
+            }
         }
     }
 
@@ -437,7 +464,7 @@
      * @throws IOException In case of reaching EOF or a stream error
      */
     private static void skip(InputStream is, int count) throws IOException {
-        for(int i = 0; i < count; i++) {
+        for (int i = 0; i < count; i++) {
             is.read(); // Do not use the InputStream.skip as it fails for some stream types
         }
     }
@@ -457,127 +484,132 @@
         boolean success = true;
         int skipLen = 0;
 
-        for(int i = 0; i < count; i++) {
+        for (int i = 0; i < count; i++) {
             paramId = is.read();
             is.read(); // Skip the reserved byte
             paramLength = is.read();
             paramLength = paramLength << 8 | is.read();
 
             // As per SAP spec padding should be 0-3 bytes
-            if ((paramLength % 4) != 0)
+            if ((paramLength % 4) != 0) {
                 skipLen = 4 - (paramLength % 4);
+            }
 
-            if(VERBOSE) Log.i(TAG, "parsing paramId: " + paramId + " with length: " + paramLength);
-            switch(paramId) {
-            case PARAM_MAX_MSG_SIZE_ID:
-                if(paramLength != PARAM_MAX_MSG_SIZE_LENGTH) {
-                    Log.e(TAG, "Received PARAM_MAX_MSG_SIZE with wrong length: " +
-                            paramLength + " skipping this parameter.");
+            if (VERBOSE) {
+                Log.i(TAG, "parsing paramId: " + paramId + " with length: " + paramLength);
+            }
+            switch (paramId) {
+                case PARAM_MAX_MSG_SIZE_ID:
+                    if (paramLength != PARAM_MAX_MSG_SIZE_LENGTH) {
+                        Log.e(TAG, "Received PARAM_MAX_MSG_SIZE with wrong length: " + paramLength
+                                + " skipping this parameter.");
+                        skip(is, paramLength + skipLen);
+                        success = false;
+                    } else {
+                        mMaxMsgSize = is.read();
+                        mMaxMsgSize = mMaxMsgSize << 8 | is.read();
+                        skip(is, 4 - PARAM_MAX_MSG_SIZE_LENGTH);
+                    }
+                    break;
+                case PARAM_COMMAND_APDU_ID:
+                    mApdu = new byte[paramLength];
+                    read(is, mApdu);
+                    skip(is, skipLen);
+                    break;
+                case PARAM_COMMAND_APDU7816_ID:
+                    mApdu7816 = new byte[paramLength];
+                    read(is, mApdu7816);
+                    skip(is, skipLen);
+                    break;
+                case PARAM_TRANSPORT_PROTOCOL_ID:
+                    if (paramLength != PARAM_TRANSPORT_PROTOCOL_LENGTH) {
+                        Log.e(TAG, "Received PARAM_TRANSPORT_PROTOCOL with wrong length: "
+                                + paramLength + " skipping this parameter.");
+                        skip(is, paramLength + skipLen);
+                        success = false;
+                    } else {
+                        mTransportProtocol = is.read();
+                        skip(is, 4 - PARAM_TRANSPORT_PROTOCOL_LENGTH);
+                    }
+                    break;
+                case PARAM_CONNECTION_STATUS_ID:
+                    // not needed for server role, but used for module test
+                    if (paramLength != PARAM_CONNECTION_STATUS_LENGTH) {
+                        Log.e(TAG,
+                                "Received PARAM_CONNECTION_STATUS with wrong length: " + paramLength
+                                        + " skipping this parameter.");
+                        skip(is, paramLength + skipLen);
+                        success = false;
+                    } else {
+                        mConnectionStatus = is.read();
+                        skip(is, 4 - PARAM_CONNECTION_STATUS_LENGTH);
+                    }
+                    break;
+                case PARAM_CARD_READER_STATUS_ID:
+                    // not needed for server role, but used for module test
+                    if (paramLength != PARAM_CARD_READER_STATUS_LENGTH) {
+                        Log.e(TAG, "Received PARAM_CARD_READER_STATUS with wrong length: "
+                                + paramLength + " skipping this parameter.");
+                        skip(is, paramLength + skipLen);
+                        success = false;
+                    } else {
+                        mCardReaderStatus = is.read();
+                        skip(is, 4 - PARAM_CARD_READER_STATUS_LENGTH);
+                    }
+                    break;
+                case PARAM_STATUS_CHANGE_ID:
+                    // not needed for server role, but used for module test
+                    if (paramLength != PARAM_STATUS_CHANGE_LENGTH) {
+                        Log.e(TAG, "Received PARAM_STATUS_CHANGE with wrong length: " + paramLength
+                                + " skipping this parameter.");
+                        skip(is, paramLength + skipLen);
+                        success = false;
+                    } else {
+                        mStatusChange = is.read();
+                        skip(is, 4 - PARAM_STATUS_CHANGE_LENGTH);
+                    }
+                    break;
+                case PARAM_RESULT_CODE_ID:
+                    // not needed for server role, but used for module test
+                    if (paramLength != PARAM_RESULT_CODE_LENGTH) {
+                        Log.e(TAG, "Received PARAM_RESULT_CODE with wrong length: " + paramLength
+                                + " skipping this parameter.");
+                        skip(is, paramLength + skipLen);
+                        success = false;
+                    } else {
+                        mResultCode = is.read();
+                        skip(is, 4 - PARAM_RESULT_CODE_LENGTH);
+                    }
+                    break;
+                case PARAM_DISCONNECT_TYPE_ID:
+                    // not needed for server role, but used for module test
+                    if (paramLength != PARAM_DISCONNECT_TYPE_LENGTH) {
+                        Log.e(TAG, "Received PARAM_DISCONNECT_TYPE_ID with wrong length: "
+                                + paramLength + " skipping this parameter.");
+                        skip(is, paramLength + skipLen);
+                        success = false;
+                    } else {
+                        mDisconnectionType = is.read();
+                        skip(is, 4 - PARAM_DISCONNECT_TYPE_LENGTH);
+                    }
+                    break;
+                case PARAM_RESPONSE_APDU_ID:
+                    // not needed for server role, but used for module test
+                    mApduResp = new byte[paramLength];
+                    read(is, mApduResp);
+                    skip(is, skipLen);
+                    break;
+                case PARAM_ATR_ID:
+                    // not needed for server role, but used for module test
+                    mAtr = new byte[paramLength];
+                    read(is, mAtr);
+                    skip(is, skipLen);
+                    break;
+                default:
+                    Log.e(TAG,
+                            "Received unknown parameter ID: " + paramId + " length: " + paramLength
+                                    + " skipping this parameter.");
                     skip(is, paramLength + skipLen);
-                    success = false;
-                } else {
-                    mMaxMsgSize = is.read();
-                    mMaxMsgSize = mMaxMsgSize << 8 | is.read();
-                    skip(is, 4 - PARAM_MAX_MSG_SIZE_LENGTH);
-                }
-                break;
-            case PARAM_COMMAND_APDU_ID:
-                mApdu = new byte[paramLength];
-                read(is, mApdu);
-                skip(is, skipLen);
-                break;
-            case PARAM_COMMAND_APDU7816_ID:
-                mApdu7816 = new byte[paramLength];
-                read(is, mApdu7816);
-                skip(is, skipLen);
-                break;
-            case PARAM_TRANSPORT_PROTOCOL_ID:
-                if(paramLength != PARAM_TRANSPORT_PROTOCOL_LENGTH) {
-                    Log.e(TAG, "Received PARAM_TRANSPORT_PROTOCOL with wrong length: " +
-                            paramLength + " skipping this parameter.");
-                    skip(is, paramLength + skipLen);
-                    success = false;
-                } else {
-                    mTransportProtocol = is.read();
-                    skip(is, 4 - PARAM_TRANSPORT_PROTOCOL_LENGTH);
-                }
-                break;
-            case PARAM_CONNECTION_STATUS_ID:
-                // not needed for server role, but used for module test
-                if(paramLength != PARAM_CONNECTION_STATUS_LENGTH) {
-                    Log.e(TAG, "Received PARAM_CONNECTION_STATUS with wrong length: " +
-                            paramLength + " skipping this parameter.");
-                    skip(is, paramLength + skipLen);
-                    success = false;
-                } else {
-                    mConnectionStatus = is.read();
-                    skip(is, 4 - PARAM_CONNECTION_STATUS_LENGTH);
-                }
-                break;
-            case PARAM_CARD_READER_STATUS_ID:
-                // not needed for server role, but used for module test
-                if(paramLength != PARAM_CARD_READER_STATUS_LENGTH) {
-                    Log.e(TAG, "Received PARAM_CARD_READER_STATUS with wrong length: " +
-                            paramLength + " skipping this parameter.");
-                    skip(is, paramLength + skipLen);
-                    success = false;
-                } else {
-                    mCardReaderStatus = is.read();
-                    skip(is, 4 - PARAM_CARD_READER_STATUS_LENGTH);
-                }
-                break;
-            case PARAM_STATUS_CHANGE_ID:
-                // not needed for server role, but used for module test
-                if(paramLength != PARAM_STATUS_CHANGE_LENGTH) {
-                    Log.e(TAG, "Received PARAM_STATUS_CHANGE with wrong length: " +
-                            paramLength + " skipping this parameter.");
-                    skip(is, paramLength + skipLen);
-                    success = false;
-                } else {
-                    mStatusChange = is.read();
-                    skip(is, 4 - PARAM_STATUS_CHANGE_LENGTH);
-                }
-                break;
-            case PARAM_RESULT_CODE_ID:
-                // not needed for server role, but used for module test
-                if(paramLength != PARAM_RESULT_CODE_LENGTH) {
-                    Log.e(TAG, "Received PARAM_RESULT_CODE with wrong length: " +
-                            paramLength + " skipping this parameter.");
-                    skip(is, paramLength + skipLen);
-                    success = false;
-                } else {
-                    mResultCode = is.read();
-                    skip(is, 4 - PARAM_RESULT_CODE_LENGTH);
-                }
-                break;
-            case PARAM_DISCONNECT_TYPE_ID:
-                // not needed for server role, but used for module test
-                if(paramLength != PARAM_DISCONNECT_TYPE_LENGTH) {
-                    Log.e(TAG, "Received PARAM_DISCONNECT_TYPE_ID with wrong length: " +
-                            paramLength + " skipping this parameter.");
-                    skip(is, paramLength + skipLen);
-                    success = false;
-                } else {
-                    mDisconnectionType = is.read();
-                    skip(is, 4 - PARAM_DISCONNECT_TYPE_LENGTH);
-                }
-                break;
-            case PARAM_RESPONSE_APDU_ID:
-                // not needed for server role, but used for module test
-                mApduResp = new byte[paramLength];
-                read(is, mApduResp);
-                skip(is, skipLen);
-                break;
-            case PARAM_ATR_ID:
-                // not needed for server role, but used for module test
-                mAtr = new byte[paramLength];
-                read(is, mAtr);
-                skip(is, skipLen);
-                break;
-            default:
-                Log.e(TAG, "Received unknown parameter ID: " + paramId + " length: " +
-                        paramLength + " skipping this parameter.");
-                skip(is, paramLength + skipLen);
             }
         }
         return success;
@@ -592,7 +624,7 @@
      * @throws IOException if the write to os fails
      */
     private static void writeParameter(OutputStream os, int id, int value, int length)
-                throws IOException {
+            throws IOException {
 
         /* Parameter Header*/
         os.write(id);
@@ -600,21 +632,21 @@
         os.write(0);
         os.write(length);
 
-        switch(length) {
-        case 1:
-            os.write(value & 0xff);
-            os.write(0); // Padding
-            os.write(0); // Padding
-            os.write(0); // padding
-            break;
-        case 2:
-            os.write((value >> 8) & 0xff);
-            os.write(value & 0xff);
-            os.write(0); // Padding
-            os.write(0); // padding
-            break;
-        default:
-            throw new IOException("Unable to write value of length: " + length);
+        switch (length) {
+            case 1:
+                os.write(value & 0xff);
+                os.write(0); // Padding
+                os.write(0); // Padding
+                os.write(0); // padding
+                break;
+            case 2:
+                os.write((value >> 8) & 0xff);
+                os.write(value & 0xff);
+                os.write(0); // Padding
+                os.write(0); // padding
+                break;
+            default:
+                throw new IOException("Unable to write value of length: " + length);
         }
     }
 
@@ -650,44 +682,41 @@
         os.write(0); // padding
 
         /* write the parameters */
-        if(mConnectionStatus != INVALID_VALUE) {
-            writeParameter(os,PARAM_CONNECTION_STATUS_ID, mConnectionStatus,
-                            PARAM_CONNECTION_STATUS_LENGTH);
+        if (mConnectionStatus != INVALID_VALUE) {
+            writeParameter(os, PARAM_CONNECTION_STATUS_ID, mConnectionStatus,
+                    PARAM_CONNECTION_STATUS_LENGTH);
         }
-        if(mMaxMsgSize != INVALID_VALUE) {
-            writeParameter(os, PARAM_MAX_MSG_SIZE_ID, mMaxMsgSize,
-                            PARAM_MAX_MSG_SIZE_LENGTH);
+        if (mMaxMsgSize != INVALID_VALUE) {
+            writeParameter(os, PARAM_MAX_MSG_SIZE_ID, mMaxMsgSize, PARAM_MAX_MSG_SIZE_LENGTH);
         }
-        if(mResultCode != INVALID_VALUE) {
-            writeParameter(os, PARAM_RESULT_CODE_ID, mResultCode,
-                            PARAM_RESULT_CODE_LENGTH);
+        if (mResultCode != INVALID_VALUE) {
+            writeParameter(os, PARAM_RESULT_CODE_ID, mResultCode, PARAM_RESULT_CODE_LENGTH);
         }
-        if(mDisconnectionType != INVALID_VALUE) {
+        if (mDisconnectionType != INVALID_VALUE) {
             writeParameter(os, PARAM_DISCONNECT_TYPE_ID, mDisconnectionType,
-                            PARAM_DISCONNECT_TYPE_LENGTH);
+                    PARAM_DISCONNECT_TYPE_LENGTH);
         }
-        if(mCardReaderStatus != INVALID_VALUE) {
+        if (mCardReaderStatus != INVALID_VALUE) {
             writeParameter(os, PARAM_CARD_READER_STATUS_ID, mCardReaderStatus,
-                            PARAM_CARD_READER_STATUS_LENGTH);
+                    PARAM_CARD_READER_STATUS_LENGTH);
         }
-        if(mStatusChange != INVALID_VALUE) {
-            writeParameter(os, PARAM_STATUS_CHANGE_ID, mStatusChange,
-                            PARAM_STATUS_CHANGE_LENGTH);
+        if (mStatusChange != INVALID_VALUE) {
+            writeParameter(os, PARAM_STATUS_CHANGE_ID, mStatusChange, PARAM_STATUS_CHANGE_LENGTH);
         }
-        if(mTransportProtocol != INVALID_VALUE) {
+        if (mTransportProtocol != INVALID_VALUE) {
             writeParameter(os, PARAM_TRANSPORT_PROTOCOL_ID, mTransportProtocol,
-                            PARAM_TRANSPORT_PROTOCOL_LENGTH);
+                    PARAM_TRANSPORT_PROTOCOL_LENGTH);
         }
-        if(mApdu != null) {
+        if (mApdu != null) {
             writeParameter(os, PARAM_COMMAND_APDU_ID, mApdu);
         }
-        if(mApdu7816 != null) {
+        if (mApdu7816 != null) {
             writeParameter(os, PARAM_COMMAND_APDU7816_ID, mApdu7816);
         }
-        if(mApduResp != null) {
+        if (mApduResp != null) {
             writeParameter(os, PARAM_RESPONSE_APDU_ID, mApduResp);
         }
-        if(mAtr != null) {
+        if (mAtr != null) {
             writeParameter(os, PARAM_ATR_ID, mAtr);
         }
     }
@@ -705,8 +734,8 @@
     private void writeLength(int length, CodedOutputStreamMicro out) throws IOException {
         byte[] dataLength = new byte[4];
         dataLength[0] = dataLength[1] = 0;
-        dataLength[2] = (byte)((length >> 8) & 0xff);
-        dataLength[3] = (byte)((length) & 0xff);
+        dataLength[2] = (byte) ((length >> 8) & 0xff);
+        dataLength[3] = (byte) ((length) & 0xff);
         out.writeRawBytes(dataLength);
     }
 
@@ -727,85 +756,78 @@
         Log.e(TAG, "callISapReq: called for mMsgType " + mMsgType + " rilSerial " + rilSerial);
 
         /* Update the ongoing requests queue */
-        if (mClearRilQueue == true) {
+        if (mClearRilQueue) {
             resetPendingRilMessages();
         }
         // No need to synchronize this, as the HashList is already doing this.
         sOngoingRequests.put(rilSerial, mMsgType);
 
-        switch(mMsgType) {
-        case ID_CONNECT_REQ:
-        {
-            sapProxy.connectReq(rilSerial, mMaxMsgSize);
-            break;
-        }
-        case ID_DISCONNECT_REQ:
-        {
-            sapProxy.disconnectReq(rilSerial);
-            break;
-        }
-        case ID_TRANSFER_APDU_REQ:
-        {
-            int type;
-            ArrayList<Byte> command;
-            if(mApdu != null) {
-                type = SapApduType.APDU;
-                command = primitiveArrayToContainerArrayList(mApdu);
-            } else if (mApdu7816 != null) {
-                type = SapApduType.APDU7816;
-                command = primitiveArrayToContainerArrayList(mApdu7816);
-            } else {
-                Log.e(TAG, "Missing Apdu parameter in TRANSFER_APDU_REQ");
-                throw new IllegalArgumentException();
+        switch (mMsgType) {
+            case ID_CONNECT_REQ: {
+                sapProxy.connectReq(rilSerial, mMaxMsgSize);
+                break;
             }
-            sapProxy.apduReq(rilSerial, type, command);
-            break;
-        }
-        case ID_SET_TRANSPORT_PROTOCOL_REQ:
-        {
-            int transportProtocol;
-            if(mTransportProtocol == TRANS_PROTO_T0) {
-                transportProtocol = SapTransferProtocol.T0;
-            } else if(mTransportProtocol == TRANS_PROTO_T1) {
-                transportProtocol = SapTransferProtocol.T1;
-            } else {
-                Log.e(TAG, "Missing or invalid TransportProtocol parameter in"
-                                + " SET_TRANSPORT_PROTOCOL_REQ: " + mTransportProtocol);
-                throw new IllegalArgumentException();
+            case ID_DISCONNECT_REQ: {
+                sapProxy.disconnectReq(rilSerial);
+                break;
             }
-            sapProxy.setTransferProtocolReq(rilSerial, transportProtocol);
-            break;
+            case ID_TRANSFER_APDU_REQ: {
+                int type;
+                ArrayList<Byte> command;
+                if (mApdu != null) {
+                    type = SapApduType.APDU;
+                    command = primitiveArrayToContainerArrayList(mApdu);
+                } else if (mApdu7816 != null) {
+                    type = SapApduType.APDU7816;
+                    command = primitiveArrayToContainerArrayList(mApdu7816);
+                } else {
+                    Log.e(TAG, "Missing Apdu parameter in TRANSFER_APDU_REQ");
+                    throw new IllegalArgumentException();
+                }
+                sapProxy.apduReq(rilSerial, type, command);
+                break;
+            }
+            case ID_SET_TRANSPORT_PROTOCOL_REQ: {
+                int transportProtocol;
+                if (mTransportProtocol == TRANS_PROTO_T0) {
+                    transportProtocol = SapTransferProtocol.T0;
+                } else if (mTransportProtocol == TRANS_PROTO_T1) {
+                    transportProtocol = SapTransferProtocol.T1;
+                } else {
+                    Log.e(TAG, "Missing or invalid TransportProtocol parameter in"
+                            + " SET_TRANSPORT_PROTOCOL_REQ: " + mTransportProtocol);
+                    throw new IllegalArgumentException();
+                }
+                sapProxy.setTransferProtocolReq(rilSerial, transportProtocol);
+                break;
+            }
+            case ID_TRANSFER_ATR_REQ: {
+                sapProxy.transferAtrReq(rilSerial);
+                break;
+            }
+            case ID_POWER_SIM_OFF_REQ: {
+                sapProxy.powerReq(rilSerial, false);
+                break;
+            }
+            case ID_POWER_SIM_ON_REQ: {
+                sapProxy.powerReq(rilSerial, true);
+                break;
+            }
+            case ID_RESET_SIM_REQ: {
+                sapProxy.resetSimReq(rilSerial);
+                break;
+            }
+            case ID_TRANSFER_CARD_READER_STATUS_REQ: {
+                sapProxy.transferCardReaderStatusReq(rilSerial);
+                break;
+            }
+            default:
+                Log.e(TAG, "Unknown request type");
+                throw new IllegalArgumentException();
         }
-        case ID_TRANSFER_ATR_REQ:
-        {
-            sapProxy.transferAtrReq(rilSerial);
-            break;
+        if (VERBOSE) {
+            Log.e(TAG, "callISapReq: done without exceptions");
         }
-        case ID_POWER_SIM_OFF_REQ:
-        {
-            sapProxy.powerReq(rilSerial, false);
-            break;
-        }
-        case ID_POWER_SIM_ON_REQ:
-        {
-            sapProxy.powerReq(rilSerial, true);
-            break;
-        }
-        case ID_RESET_SIM_REQ:
-        {
-            sapProxy.resetSimReq(rilSerial);
-            break;
-        }
-        case ID_TRANSFER_CARD_READER_STATUS_REQ:
-        {
-            sapProxy.transferCardReaderStatusReq(rilSerial);
-            break;
-        }
-        default:
-            Log.e(TAG, "Unknown request type");
-            throw new IllegalArgumentException();
-        }
-        if (VERBOSE) Log.e(TAG, "callISapReq: done without exceptions");
     }
 
     public static SapMessage newInstance(MsgHeader msg) throws IOException {
@@ -814,16 +836,16 @@
 
     private SapMessage(MsgHeader msg) throws IOException {
         // All header members are "required" hence the hasXxxx() is not needed for those
-        try{
-            switch(msg.getType()){
-            case SapApi.UNSOL_RESPONSE:
-                createUnsolicited(msg);
-                break;
-            case SapApi.RESPONSE:
-                createSolicited(msg);
-                break;
-            default:
-                throw new IOException("Wrong msg header received: Type: " + msg.getType());
+        try {
+            switch (msg.getType()) {
+                case SapApi.UNSOL_RESPONSE:
+                    createUnsolicited(msg);
+                    break;
+                case SapApi.RESPONSE:
+                    createSolicited(msg);
+                    break;
+                default:
+                    throw new IOException("Wrong msg header received: Type: " + msg.getType());
             }
         } catch (InvalidProtocolBufferMicroException e) {
             Log.w(TAG, "Error occured parsing a RIL message", e);
@@ -832,65 +854,83 @@
     }
 
     private void createUnsolicited(MsgHeader msg)
-                    throws IOException, InvalidProtocolBufferMicroException {
-        switch(msg.getId()) {
+            throws IOException, InvalidProtocolBufferMicroException {
+        switch (msg.getId()) {
 // TODO:
 //        Not sure when we use these?        case RIL_UNSOL_RIL_CONNECTED:
 //            if(VERBOSE) Log.i(TAG, "RIL_UNSOL_RIL_CONNECTED received, ignoring");
 //            msgType = ID_RIL_UNSOL_CONNECTED;
 //            break;
-        case SapApi.RIL_SIM_SAP_STATUS:
-        {
-            if(VERBOSE) Log.i(TAG, "RIL_SIM_SAP_STATUS_IND received");
-            RIL_SIM_SAP_STATUS_IND indMsg =
-                    RIL_SIM_SAP_STATUS_IND.parseFrom(msg.getPayload().toByteArray());
-            mMsgType = ID_STATUS_IND;
-            if(indMsg.hasStatusChange()) {
-                setStatusChange(indMsg.getStatusChange());
-                if(VERBOSE) Log.i(TAG, "RIL_UNSOL_SIM_SAP_STATUS_IND received value = "
-                        + mStatusChange);
-            } else {
-                if(VERBOSE) Log.i(TAG, "Wrong number of parameters in SAP_STATUS_IND, ignoring...");
-                mMsgType = ID_RIL_UNKNOWN;
+            case SapApi.RIL_SIM_SAP_STATUS: {
+                if (VERBOSE) {
+                    Log.i(TAG, "RIL_SIM_SAP_STATUS_IND received");
+                }
+                RIL_SIM_SAP_STATUS_IND indMsg =
+                        RIL_SIM_SAP_STATUS_IND.parseFrom(msg.getPayload().toByteArray());
+                mMsgType = ID_STATUS_IND;
+                if (indMsg.hasStatusChange()) {
+                    setStatusChange(indMsg.getStatusChange());
+                    if (VERBOSE) {
+                        Log.i(TAG,
+                                "RIL_UNSOL_SIM_SAP_STATUS_IND received value = " + mStatusChange);
+                    }
+                } else {
+                    if (VERBOSE) {
+                        Log.i(TAG, "Wrong number of parameters in SAP_STATUS_IND, ignoring...");
+                    }
+                    mMsgType = ID_RIL_UNKNOWN;
+                }
+                break;
             }
-            break;
-        }
-        case SapApi.RIL_SIM_SAP_DISCONNECT:
-        {
-            if(VERBOSE) Log.i(TAG, "RIL_SIM_SAP_DISCONNECT_IND received");
+            case SapApi.RIL_SIM_SAP_DISCONNECT: {
+                if (VERBOSE) {
+                    Log.i(TAG, "RIL_SIM_SAP_DISCONNECT_IND received");
+                }
 
-            RIL_SIM_SAP_DISCONNECT_IND indMsg =
-                    RIL_SIM_SAP_DISCONNECT_IND.parseFrom(msg.getPayload().toByteArray());
-            mMsgType = ID_RIL_UNSOL_DISCONNECT_IND; // don't use ID_DISCONNECT_IND;
-            if(indMsg.hasDisconnectType()) {
-                setDisconnectionType(indMsg.getDisconnectType());
-                if(VERBOSE) Log.i(TAG, "RIL_UNSOL_SIM_SAP_STATUS_IND received value = "
-                                                                + mDisconnectionType);
-            } else {
-                if(VERBOSE) Log.i(TAG, "Wrong number of parameters in SAP_STATUS_IND, ignoring...");
-                mMsgType = ID_RIL_UNKNOWN;
+                RIL_SIM_SAP_DISCONNECT_IND indMsg =
+                        RIL_SIM_SAP_DISCONNECT_IND.parseFrom(msg.getPayload().toByteArray());
+                mMsgType = ID_RIL_UNSOL_DISCONNECT_IND; // don't use ID_DISCONNECT_IND;
+                if (indMsg.hasDisconnectType()) {
+                    setDisconnectionType(indMsg.getDisconnectType());
+                    if (VERBOSE) {
+                        Log.i(TAG, "RIL_UNSOL_SIM_SAP_STATUS_IND received value = "
+                                + mDisconnectionType);
+                    }
+                } else {
+                    if (VERBOSE) {
+                        Log.i(TAG, "Wrong number of parameters in SAP_STATUS_IND, ignoring...");
+                    }
+                    mMsgType = ID_RIL_UNKNOWN;
+                }
+                break;
             }
-            break;
-        }
-        default:
-            if(VERBOSE) Log.i(TAG, "Unused unsolicited message received, ignoring: " + msg.getId());
-            mMsgType = ID_RIL_UNKNOWN;
+            default:
+                if (VERBOSE) {
+                    Log.i(TAG, "Unused unsolicited message received, ignoring: " + msg.getId());
+                }
+                mMsgType = ID_RIL_UNKNOWN;
         }
     }
 
-    private void createSolicited(MsgHeader msg) throws IOException,
-                                                       InvalidProtocolBufferMicroException{
+    private void createSolicited(MsgHeader msg)
+            throws IOException, InvalidProtocolBufferMicroException {
         /* re-evaluate if we should just ignore these - we could simply catch the exception? */
-        if(msg.hasToken() == false) throw new IOException("Token is missing");
-        if(msg.hasError() == false) throw new IOException("Error code is missing");
+        if (!msg.hasToken()) {
+            throw new IOException("Token is missing");
+        }
+        if (!msg.hasError()) {
+            throw new IOException("Error code is missing");
+        }
         int serial = msg.getToken();
         int error = msg.getError();
         Integer reqType = null;
         reqType = sOngoingRequests.remove(serial);
-        if(VERBOSE) Log.i(TAG, "RIL SOLICITED serial: " + serial + ", error: " + error
-                + " SapReqType: " + ((reqType== null)?"null":getMsgTypeName(reqType)));
+        if (VERBOSE) {
+            Log.i(TAG, "RIL SOLICITED serial: " + serial + ", error: " + error + " SapReqType: " + (
+                    (reqType == null) ? "null" : getMsgTypeName(reqType)));
+        }
 
-        if(reqType == null) {
+        if (reqType == null) {
             /* This can happen if we get a resp. for a canceled request caused by a power off,
              *  reset or disconnect
              */
@@ -899,309 +939,327 @@
         }
         mResultCode = mapRilErrorCode(error);
 
-        switch(reqType) {
-        case ID_CONNECT_REQ:
-        {
-            RIL_SIM_SAP_CONNECT_RSP resMsg =
-                    RIL_SIM_SAP_CONNECT_RSP.parseFrom(msg.getPayload().toByteArray());
-            mMsgType = ID_CONNECT_RESP;
-            if(resMsg.hasMaxMessageSize()) {
-                mMaxMsgSize = resMsg.getMaxMessageSize();
+        switch (reqType) {
+            case ID_CONNECT_REQ: {
+                RIL_SIM_SAP_CONNECT_RSP resMsg =
+                        RIL_SIM_SAP_CONNECT_RSP.parseFrom(msg.getPayload().toByteArray());
+                mMsgType = ID_CONNECT_RESP;
+                if (resMsg.hasMaxMessageSize()) {
+                    mMaxMsgSize = resMsg.getMaxMessageSize();
 
-            }
-            switch(resMsg.getResponse()) {
-            case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SUCCESS:
-                mConnectionStatus = CON_STATUS_OK;
-                break;
-            case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_CONNECT_OK_CALL_ONGOING:
-                mConnectionStatus = CON_STATUS_OK_ONGOING_CALL;
-                break;
-            case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_CONNECT_FAILURE:
-                mConnectionStatus = CON_STATUS_ERROR_CONNECTION;
-                break;
-            case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_MSG_SIZE_TOO_LARGE:
-                mConnectionStatus = CON_STATUS_ERROR_MAX_MSG_SIZE_UNSUPPORTED;
-                break;
-            case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_MSG_SIZE_TOO_SMALL:
-                mConnectionStatus = CON_STATUS_ERROR_MAX_MSG_SIZE_TOO_SMALL;
-                break;
-            default:
-                mConnectionStatus = CON_STATUS_ERROR_CONNECTION; // Cannot happen!
-                break;
-            }
-            mResultCode = INVALID_VALUE;
-            if(VERBOSE) Log.v(TAG, "  ID_CONNECT_REQ: mMaxMsgSize: " + mMaxMsgSize
-                    + "  mConnectionStatus: " + mConnectionStatus);
-            break;
-        }
-        case ID_DISCONNECT_REQ:
-            mMsgType = ID_DISCONNECT_RESP;
-            mResultCode = INVALID_VALUE;
-            break;
-        case ID_TRANSFER_APDU_REQ:
-        {
-            RIL_SIM_SAP_APDU_RSP resMsg =
-                    RIL_SIM_SAP_APDU_RSP.parseFrom(msg.getPayload().toByteArray());
-            mMsgType = ID_TRANSFER_APDU_RESP;
-            switch(resMsg.getResponse()) {
-            case RIL_SIM_SAP_APDU_RSP.RIL_E_SUCCESS:
-                mResultCode = RESULT_OK;
-                /* resMsg.getType is unused as the client knows the type of request used. */
-                if(resMsg.hasApduResponse()){
-                    mApduResp = resMsg.getApduResponse().toByteArray();
+                }
+                switch (resMsg.getResponse()) {
+                    case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SUCCESS:
+                        mConnectionStatus = CON_STATUS_OK;
+                        break;
+                    case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_CONNECT_OK_CALL_ONGOING:
+                        mConnectionStatus = CON_STATUS_OK_ONGOING_CALL;
+                        break;
+                    case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_CONNECT_FAILURE:
+                        mConnectionStatus = CON_STATUS_ERROR_CONNECTION;
+                        break;
+                    case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_MSG_SIZE_TOO_LARGE:
+                        mConnectionStatus = CON_STATUS_ERROR_MAX_MSG_SIZE_UNSUPPORTED;
+                        break;
+                    case RIL_SIM_SAP_CONNECT_RSP.RIL_E_SAP_MSG_SIZE_TOO_SMALL:
+                        mConnectionStatus = CON_STATUS_ERROR_MAX_MSG_SIZE_TOO_SMALL;
+                        break;
+                    default:
+                        mConnectionStatus = CON_STATUS_ERROR_CONNECTION; // Cannot happen!
+                        break;
+                }
+                mResultCode = INVALID_VALUE;
+                if (VERBOSE) {
+                    Log.v(TAG, "  ID_CONNECT_REQ: mMaxMsgSize: " + mMaxMsgSize
+                            + "  mConnectionStatus: " + mConnectionStatus);
                 }
                 break;
-            case RIL_SIM_SAP_APDU_RSP.RIL_E_GENERIC_FAILURE:
-                mResultCode = RESULT_ERROR_NO_REASON;
+            }
+            case ID_DISCONNECT_REQ:
+                mMsgType = ID_DISCONNECT_RESP;
+                mResultCode = INVALID_VALUE;
                 break;
-            case RIL_SIM_SAP_APDU_RSP.RIL_E_SIM_ABSENT:
-                mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
-                break;
-            case RIL_SIM_SAP_APDU_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
-                mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
-                break;
-            case RIL_SIM_SAP_APDU_RSP.RIL_E_SIM_NOT_READY:
-                mResultCode = RESULT_ERROR_CARD_REMOVED;
-                break;
-            default:
-                mResultCode = RESULT_ERROR_NO_REASON;
+            case ID_TRANSFER_APDU_REQ: {
+                RIL_SIM_SAP_APDU_RSP resMsg =
+                        RIL_SIM_SAP_APDU_RSP.parseFrom(msg.getPayload().toByteArray());
+                mMsgType = ID_TRANSFER_APDU_RESP;
+                switch (resMsg.getResponse()) {
+                    case RIL_SIM_SAP_APDU_RSP.RIL_E_SUCCESS:
+                        mResultCode = RESULT_OK;
+                /* resMsg.getType is unused as the client knows the type of request used. */
+                        if (resMsg.hasApduResponse()) {
+                            mApduResp = resMsg.getApduResponse().toByteArray();
+                        }
+                        break;
+                    case RIL_SIM_SAP_APDU_RSP.RIL_E_GENERIC_FAILURE:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                    case RIL_SIM_SAP_APDU_RSP.RIL_E_SIM_ABSENT:
+                        mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+                        break;
+                    case RIL_SIM_SAP_APDU_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+                        mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+                        break;
+                    case RIL_SIM_SAP_APDU_RSP.RIL_E_SIM_NOT_READY:
+                        mResultCode = RESULT_ERROR_CARD_REMOVED;
+                        break;
+                    default:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                }
                 break;
             }
-            break;
-        }
-        case ID_SET_TRANSPORT_PROTOCOL_REQ:
-        {
-            RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP resMsg =
+            case ID_SET_TRANSPORT_PROTOCOL_REQ: {
+                RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP resMsg =
                         RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.parseFrom(
                                 msg.getPayload().toByteArray());
-            mMsgType = ID_SET_TRANSPORT_PROTOCOL_RESP;
-            switch(resMsg.getResponse()) {
-            case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SUCCESS:
-                mResultCode = RESULT_OK;
-                break;
-            case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_GENERIC_FAILURE:
-                mResultCode = RESULT_ERROR_NOT_SUPPORTED;
-                break;
-            case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SIM_ABSENT:
-                mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
-                break;
-            case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
-                mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
-                break;
-            case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SIM_NOT_READY:
-                mResultCode = RESULT_ERROR_CARD_REMOVED;
-                break;
-            default:
-                mResultCode = RESULT_ERROR_NOT_SUPPORTED;
-                break;
-            }
-            break;
-        }
-        case ID_TRANSFER_ATR_REQ:
-        {
-            RIL_SIM_SAP_TRANSFER_ATR_RSP resMsg =
-                    RIL_SIM_SAP_TRANSFER_ATR_RSP.parseFrom(msg.getPayload().toByteArray());
-            mMsgType =ID_TRANSFER_ATR_RESP;
-            if(resMsg.hasAtr()) {
-                mAtr = resMsg.getAtr().toByteArray();
-            }
-            switch(resMsg.getResponse()) {
-            case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SUCCESS:
-                mResultCode = RESULT_OK;
-                break;
-            case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_GENERIC_FAILURE:
-                mResultCode = RESULT_ERROR_NO_REASON;
-                break;
-            case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_ABSENT:
-                mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
-                break;
-            case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
-                mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
-                break;
-            case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_ALREADY_POWERED_ON:
-                mResultCode = RESULT_ERROR_CARD_POWERED_ON;
-                break;
-            case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_DATA_NOT_AVAILABLE:
-                mResultCode = RESULT_ERROR_DATA_NOT_AVAILABLE;
-                break;
-            default:
-                mResultCode = RESULT_ERROR_NO_REASON;
-                break;
-            }
-            break;
-        }
-        case ID_POWER_SIM_OFF_REQ:
-        {
-            RIL_SIM_SAP_POWER_RSP resMsg =
-                    RIL_SIM_SAP_POWER_RSP.parseFrom(msg.getPayload().toByteArray());
-            mMsgType = ID_POWER_SIM_OFF_RESP;
-            switch(resMsg.getResponse()) {
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_SUCCESS:
-                mResultCode = RESULT_OK;
-                break;
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_GENERIC_FAILURE:
-                mResultCode = RESULT_ERROR_NO_REASON;
-                break;
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ABSENT:
-                mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
-                break;
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
-                mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
-                break;
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_ON:
-                mResultCode = RESULT_ERROR_CARD_POWERED_ON;
-                break;
-            default:
-                mResultCode = RESULT_ERROR_NO_REASON;
-                break;
-            }
-            break;
-        }
-        case ID_POWER_SIM_ON_REQ:
-        {
-            RIL_SIM_SAP_POWER_RSP resMsg =
-                    RIL_SIM_SAP_POWER_RSP.parseFrom(msg.getPayload().toByteArray());
-            mMsgType = ID_POWER_SIM_ON_RESP;
-            switch(resMsg.getResponse()) {
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_SUCCESS:
-                mResultCode = RESULT_OK;
-                break;
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_GENERIC_FAILURE:
-                mResultCode = RESULT_ERROR_NO_REASON;
-                break;
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ABSENT:
-                mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
-                break;
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
-                mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
-                break;
-            case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_ON:
-                mResultCode = RESULT_ERROR_CARD_POWERED_ON;
-                break;
-            default:
-                mResultCode = RESULT_ERROR_NO_REASON;
-                break;
-            }
-            break;
-        }
-        case ID_RESET_SIM_REQ:
-        {
-            RIL_SIM_SAP_RESET_SIM_RSP resMsg =
-                    RIL_SIM_SAP_RESET_SIM_RSP.parseFrom(msg.getPayload().toByteArray());
-            mMsgType = ID_RESET_SIM_RESP;
-            switch(resMsg.getResponse()) {
-            case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_SUCCESS:
-                mResultCode = RESULT_OK;
-                break;
-            case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_GENERIC_FAILURE:
-                mResultCode = RESULT_ERROR_NO_REASON;
-                break;
-            case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_SIM_ABSENT:
-                mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
-                break;
-            case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
-                mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
-                break;
-            default:
-                mResultCode = RESULT_ERROR_NO_REASON;
-                break;
-            }
-            break;
-        }
-        case ID_TRANSFER_CARD_READER_STATUS_REQ:
-        {
-            RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP resMsg =
-                    RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.parseFrom(
-                            msg.getPayload().toByteArray());
-            mMsgType = ID_TRANSFER_CARD_READER_STATUS_RESP;
-            switch(resMsg.getResponse()) {
-            case RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.RIL_E_SUCCESS:
-                mResultCode = RESULT_OK;
-                if(resMsg.hasCardReaderStatus()) {
-                    mCardReaderStatus = resMsg.getCardReaderStatus();
-                } else {
-                    mResultCode = RESULT_ERROR_DATA_NOT_AVAILABLE;
+                mMsgType = ID_SET_TRANSPORT_PROTOCOL_RESP;
+                switch (resMsg.getResponse()) {
+                    case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SUCCESS:
+                        mResultCode = RESULT_OK;
+                        break;
+                    case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_GENERIC_FAILURE:
+                        mResultCode = RESULT_ERROR_NOT_SUPPORTED;
+                        break;
+                    case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SIM_ABSENT:
+                        mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+                        break;
+                    case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+                        mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+                        break;
+                    case RIL_SIM_SAP_SET_TRANSFER_PROTOCOL_RSP.RIL_E_SIM_NOT_READY:
+                        mResultCode = RESULT_ERROR_CARD_REMOVED;
+                        break;
+                    default:
+                        mResultCode = RESULT_ERROR_NOT_SUPPORTED;
+                        break;
                 }
                 break;
-            case RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.RIL_E_GENERIC_FAILURE:
-                mResultCode = RESULT_ERROR_NO_REASON;
-                break;
-            case RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.RIL_E_SIM_DATA_NOT_AVAILABLE:
-                mResultCode = RESULT_ERROR_DATA_NOT_AVAILABLE;
-                break;
-            default:
-                mResultCode = RESULT_ERROR_NO_REASON;
+            }
+            case ID_TRANSFER_ATR_REQ: {
+                RIL_SIM_SAP_TRANSFER_ATR_RSP resMsg =
+                        RIL_SIM_SAP_TRANSFER_ATR_RSP.parseFrom(msg.getPayload().toByteArray());
+                mMsgType = ID_TRANSFER_ATR_RESP;
+                if (resMsg.hasAtr()) {
+                    mAtr = resMsg.getAtr().toByteArray();
+                }
+                switch (resMsg.getResponse()) {
+                    case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SUCCESS:
+                        mResultCode = RESULT_OK;
+                        break;
+                    case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_GENERIC_FAILURE:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                    case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_ABSENT:
+                        mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+                        break;
+                    case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+                        mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+                        break;
+                    case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_ALREADY_POWERED_ON:
+                        mResultCode = RESULT_ERROR_CARD_POWERED_ON;
+                        break;
+                    case RIL_SIM_SAP_TRANSFER_ATR_RSP.RIL_E_SIM_DATA_NOT_AVAILABLE:
+                        mResultCode = RESULT_ERROR_DATA_NOT_AVAILABLE;
+                        break;
+                    default:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                }
                 break;
             }
-            break;
-        }
+            case ID_POWER_SIM_OFF_REQ: {
+                RIL_SIM_SAP_POWER_RSP resMsg =
+                        RIL_SIM_SAP_POWER_RSP.parseFrom(msg.getPayload().toByteArray());
+                mMsgType = ID_POWER_SIM_OFF_RESP;
+                switch (resMsg.getResponse()) {
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_SUCCESS:
+                        mResultCode = RESULT_OK;
+                        break;
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_GENERIC_FAILURE:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ABSENT:
+                        mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+                        break;
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+                        mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+                        break;
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_ON:
+                        mResultCode = RESULT_ERROR_CARD_POWERED_ON;
+                        break;
+                    default:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                }
+                break;
+            }
+            case ID_POWER_SIM_ON_REQ: {
+                RIL_SIM_SAP_POWER_RSP resMsg =
+                        RIL_SIM_SAP_POWER_RSP.parseFrom(msg.getPayload().toByteArray());
+                mMsgType = ID_POWER_SIM_ON_RESP;
+                switch (resMsg.getResponse()) {
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_SUCCESS:
+                        mResultCode = RESULT_OK;
+                        break;
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_GENERIC_FAILURE:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ABSENT:
+                        mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+                        break;
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+                        mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+                        break;
+                    case RIL_SIM_SAP_POWER_RSP.RIL_E_SIM_ALREADY_POWERED_ON:
+                        mResultCode = RESULT_ERROR_CARD_POWERED_ON;
+                        break;
+                    default:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                }
+                break;
+            }
+            case ID_RESET_SIM_REQ: {
+                RIL_SIM_SAP_RESET_SIM_RSP resMsg =
+                        RIL_SIM_SAP_RESET_SIM_RSP.parseFrom(msg.getPayload().toByteArray());
+                mMsgType = ID_RESET_SIM_RESP;
+                switch (resMsg.getResponse()) {
+                    case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_SUCCESS:
+                        mResultCode = RESULT_OK;
+                        break;
+                    case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_GENERIC_FAILURE:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                    case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_SIM_ABSENT:
+                        mResultCode = RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+                        break;
+                    case RIL_SIM_SAP_RESET_SIM_RSP.RIL_E_SIM_ALREADY_POWERED_OFF:
+                        mResultCode = RESULT_ERROR_CARD_POWERED_OFF;
+                        break;
+                    default:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                }
+                break;
+            }
+            case ID_TRANSFER_CARD_READER_STATUS_REQ: {
+                RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP resMsg =
+                        RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.parseFrom(
+                                msg.getPayload().toByteArray());
+                mMsgType = ID_TRANSFER_CARD_READER_STATUS_RESP;
+                switch (resMsg.getResponse()) {
+                    case RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.RIL_E_SUCCESS:
+                        mResultCode = RESULT_OK;
+                        if (resMsg.hasCardReaderStatus()) {
+                            mCardReaderStatus = resMsg.getCardReaderStatus();
+                        } else {
+                            mResultCode = RESULT_ERROR_DATA_NOT_AVAILABLE;
+                        }
+                        break;
+                    case RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.RIL_E_GENERIC_FAILURE:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                    case RIL_SIM_SAP_TRANSFER_CARD_READER_STATUS_RSP.RIL_E_SIM_DATA_NOT_AVAILABLE:
+                        mResultCode = RESULT_ERROR_DATA_NOT_AVAILABLE;
+                        break;
+                    default:
+                        mResultCode = RESULT_ERROR_NO_REASON;
+                        break;
+                }
+                break;
+            }
 
-        case ID_RIL_SIM_ACCESS_TEST_REQ: // TODO: implement in RILD
-            mMsgType = ID_RIL_SIM_ACCESS_TEST_RESP;
-            break;
-        default:
-            Log.e(TAG, "Unknown request type: " + reqType);
+            case ID_RIL_SIM_ACCESS_TEST_REQ: // TODO: implement in RILD
+                mMsgType = ID_RIL_SIM_ACCESS_TEST_RESP;
+                break;
+            default:
+                Log.e(TAG, "Unknown request type: " + reqType);
 
         }
     }
 
 
-
     /* Map from RIL header error codes to SAP error codes */
     private static int mapRilErrorCode(int rilErrorCode) {
-        switch(rilErrorCode) {
-        case SapApi.RIL_E_SUCCESS:
-            return RESULT_OK;
-        case SapApi.RIL_E_CANCELLED:
-            return RESULT_ERROR_NO_REASON;
-        case SapApi.RIL_E_GENERIC_FAILURE:
-            return RESULT_ERROR_NO_REASON;
-        case SapApi.RIL_E_RADIO_NOT_AVAILABLE:
-            return RESULT_ERROR_CARD_NOT_ACCESSIBLE;
-        case SapApi.RIL_E_INVALID_PARAMETER:
-            return RESULT_ERROR_NO_REASON;
-        case SapApi.RIL_E_REQUEST_NOT_SUPPORTED:
-            return RESULT_ERROR_NOT_SUPPORTED;
-        default:
-            return RESULT_ERROR_NO_REASON;
+        switch (rilErrorCode) {
+            case SapApi.RIL_E_SUCCESS:
+                return RESULT_OK;
+            case SapApi.RIL_E_CANCELLED:
+                return RESULT_ERROR_NO_REASON;
+            case SapApi.RIL_E_GENERIC_FAILURE:
+                return RESULT_ERROR_NO_REASON;
+            case SapApi.RIL_E_RADIO_NOT_AVAILABLE:
+                return RESULT_ERROR_CARD_NOT_ACCESSIBLE;
+            case SapApi.RIL_E_INVALID_PARAMETER:
+                return RESULT_ERROR_NO_REASON;
+            case SapApi.RIL_E_REQUEST_NOT_SUPPORTED:
+                return RESULT_ERROR_NOT_SUPPORTED;
+            default:
+                return RESULT_ERROR_NO_REASON;
         }
     }
 
 
-
     public static String getMsgTypeName(int msgType) {
-        if(DEBUG || VERBOSE) {
-            switch (msgType)
-            {
-                case ID_CONNECT_REQ: return "ID_CONNECT_REQ";
-                case ID_CONNECT_RESP: return "ID_CONNECT_RESP";
-                case ID_DISCONNECT_REQ: return "ID_DISCONNECT_REQ";
-                case ID_DISCONNECT_RESP: return "ID_DISCONNECT_RESP";
-                case ID_DISCONNECT_IND: return "ID_DISCONNECT_IND";
-                case ID_TRANSFER_APDU_REQ: return "ID_TRANSFER_APDU_REQ";
-                case ID_TRANSFER_APDU_RESP: return "ID_TRANSFER_APDU_RESP";
-                case ID_TRANSFER_ATR_REQ: return "ID_TRANSFER_ATR_REQ";
-                case ID_TRANSFER_ATR_RESP: return "ID_TRANSFER_ATR_RESP";
-                case ID_POWER_SIM_OFF_REQ: return "ID_POWER_SIM_OFF_REQ";
-                case ID_POWER_SIM_OFF_RESP: return "ID_POWER_SIM_OFF_RESP";
-                case ID_POWER_SIM_ON_REQ: return "ID_POWER_SIM_ON_REQ";
-                case ID_POWER_SIM_ON_RESP: return "ID_POWER_SIM_ON_RESP";
-                case ID_RESET_SIM_REQ: return "ID_RESET_SIM_REQ";
-                case ID_RESET_SIM_RESP: return "ID_RESET_SIM_RESP";
+        if (DEBUG || VERBOSE) {
+            switch (msgType) {
+                case ID_CONNECT_REQ:
+                    return "ID_CONNECT_REQ";
+                case ID_CONNECT_RESP:
+                    return "ID_CONNECT_RESP";
+                case ID_DISCONNECT_REQ:
+                    return "ID_DISCONNECT_REQ";
+                case ID_DISCONNECT_RESP:
+                    return "ID_DISCONNECT_RESP";
+                case ID_DISCONNECT_IND:
+                    return "ID_DISCONNECT_IND";
+                case ID_TRANSFER_APDU_REQ:
+                    return "ID_TRANSFER_APDU_REQ";
+                case ID_TRANSFER_APDU_RESP:
+                    return "ID_TRANSFER_APDU_RESP";
+                case ID_TRANSFER_ATR_REQ:
+                    return "ID_TRANSFER_ATR_REQ";
+                case ID_TRANSFER_ATR_RESP:
+                    return "ID_TRANSFER_ATR_RESP";
+                case ID_POWER_SIM_OFF_REQ:
+                    return "ID_POWER_SIM_OFF_REQ";
+                case ID_POWER_SIM_OFF_RESP:
+                    return "ID_POWER_SIM_OFF_RESP";
+                case ID_POWER_SIM_ON_REQ:
+                    return "ID_POWER_SIM_ON_REQ";
+                case ID_POWER_SIM_ON_RESP:
+                    return "ID_POWER_SIM_ON_RESP";
+                case ID_RESET_SIM_REQ:
+                    return "ID_RESET_SIM_REQ";
+                case ID_RESET_SIM_RESP:
+                    return "ID_RESET_SIM_RESP";
                 case ID_TRANSFER_CARD_READER_STATUS_REQ:
                     return "ID_TRANSFER_CARD_READER_STATUS_REQ";
                 case ID_TRANSFER_CARD_READER_STATUS_RESP:
                     return "ID_TRANSFER_CARD_READER_STATUS_RESP";
-                case ID_STATUS_IND: return "ID_STATUS_IND";
-                case ID_ERROR_RESP: return "ID_ERROR_RESP";
-                case ID_SET_TRANSPORT_PROTOCOL_REQ: return "ID_SET_TRANSPORT_PROTOCOL_REQ";
-                case ID_SET_TRANSPORT_PROTOCOL_RESP: return "ID_SET_TRANSPORT_PROTOCOL_RESP";
-                case ID_RIL_UNSOL_CONNECTED: return "ID_RIL_UNSOL_CONNECTED";
-                case ID_RIL_UNKNOWN: return "ID_RIL_UNKNOWN";
-                case ID_RIL_GET_SIM_STATUS_REQ: return "ID_RIL_GET_SIM_STATUS_REQ";
-                case ID_RIL_SIM_ACCESS_TEST_REQ: return "ID_RIL_SIM_ACCESS_TEST_REQ";
-                case ID_RIL_SIM_ACCESS_TEST_RESP: return "ID_RIL_SIM_ACCESS_TEST_RESP";
-                default: return "Unknown Message Type (" + msgType + ")";
+                case ID_STATUS_IND:
+                    return "ID_STATUS_IND";
+                case ID_ERROR_RESP:
+                    return "ID_ERROR_RESP";
+                case ID_SET_TRANSPORT_PROTOCOL_REQ:
+                    return "ID_SET_TRANSPORT_PROTOCOL_REQ";
+                case ID_SET_TRANSPORT_PROTOCOL_RESP:
+                    return "ID_SET_TRANSPORT_PROTOCOL_RESP";
+                case ID_RIL_UNSOL_CONNECTED:
+                    return "ID_RIL_UNSOL_CONNECTED";
+                case ID_RIL_UNSOL_DISCONNECT_IND:
+                    return "ID_RIL_UNSOL_DISCONNECT_IND";
+                case ID_RIL_UNKNOWN:
+                    return "ID_RIL_UNKNOWN";
+                case ID_RIL_GET_SIM_STATUS_REQ:
+                    return "ID_RIL_GET_SIM_STATUS_REQ";
+                case ID_RIL_SIM_ACCESS_TEST_REQ:
+                    return "ID_RIL_SIM_ACCESS_TEST_REQ";
+                case ID_RIL_SIM_ACCESS_TEST_RESP:
+                    return "ID_RIL_SIM_ACCESS_TEST_RESP";
+                default:
+                    return "Unknown Message Type (" + msgType + ")";
             }
         } else {
             return null;
diff --git a/src/com/android/bluetooth/sap/SapRilReceiver.java b/src/com/android/bluetooth/sap/SapRilReceiver.java
index ed1f869..c21778b 100644
--- a/src/com/android/bluetooth/sap/SapRilReceiver.java
+++ b/src/com/android/bluetooth/sap/SapRilReceiver.java
@@ -1,27 +1,19 @@
 package com.android.bluetooth.sap;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.android.btsap.SapApi.MsgHeader;
-
-import com.google.protobuf.micro.CodedInputStreamMicro;
-import com.google.protobuf.micro.CodedOutputStreamMicro;
-
 import android.hardware.radio.V1_0.ISap;
 import android.hardware.radio.V1_0.ISapCallback;
-
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
 import android.os.Handler;
 import android.os.HwBinder;
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
 public class SapRilReceiver {
     private static final String TAG = "SapRilReceiver";
     public static final boolean DEBUG = true;
@@ -41,7 +33,7 @@
     private Handler mSapServiceHandler = null;
 
     public static final int RIL_MAX_COMMAND_BYTES = (8 * 1024);
-    byte[] buffer = new byte[RIL_MAX_COMMAND_BYTES];
+    public byte[] buffer = new byte[RIL_MAX_COMMAND_BYTES];
 
     final class SapProxyDeathRecipient implements HwBinder.DeathRecipient {
         @Override
@@ -67,16 +59,17 @@
     private void removeOngoingReqAndSendMessage(int token, SapMessage sapMessage) {
         Integer reqType = SapMessage.sOngoingRequests.remove(token);
         if (VERBOSE) {
-            Log.d(TAG, "removeOngoingReqAndSendMessage: token " + token + " reqType "
-                            + (reqType == null ? "null" : SapMessage.getMsgTypeName(reqType)));
+            Log.d(TAG, "removeOngoingReqAndSendMessage: token " + token + " reqType " + (
+                    reqType == null ? "null" : SapMessage.getMsgTypeName(reqType)));
         }
         sendSapMessage(sapMessage);
     }
 
     class SapCallback extends ISapCallback.Stub {
+        @Override
         public void connectResponse(int token, int sapConnectRsp, int maxMsgSize) {
             Log.d(TAG, "connectResponse: token " + token + " sapConnectRsp " + sapConnectRsp
-                            + " maxMsgSize " + maxMsgSize);
+                    + " maxMsgSize " + maxMsgSize);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
             SapMessage sapMessage = new SapMessage(SapMessage.ID_CONNECT_RESP);
             sapMessage.setConnectionStatus(sapConnectRsp);
@@ -87,6 +80,7 @@
             removeOngoingReqAndSendMessage(token, sapMessage);
         }
 
+        @Override
         public void disconnectResponse(int token) {
             Log.d(TAG, "disconnectResponse: token " + token);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
@@ -95,6 +89,7 @@
             removeOngoingReqAndSendMessage(token, sapMessage);
         }
 
+        @Override
         public void disconnectIndication(int token, int disconnectType) {
             Log.d(TAG,
                     "disconnectIndication: token " + token + " disconnectType " + disconnectType);
@@ -104,6 +99,7 @@
             sendSapMessage(sapMessage);
         }
 
+        @Override
         public void apduResponse(int token, int resultCode, ArrayList<Byte> apduRsp) {
             Log.d(TAG, "apduResponse: token " + token);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
@@ -115,6 +111,7 @@
             removeOngoingReqAndSendMessage(token, sapMessage);
         }
 
+        @Override
         public void transferAtrResponse(int token, int resultCode, ArrayList<Byte> atr) {
             Log.d(TAG, "transferAtrResponse: token " + token + " resultCode " + resultCode);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
@@ -126,13 +123,14 @@
             removeOngoingReqAndSendMessage(token, sapMessage);
         }
 
+        @Override
         public void powerResponse(int token, int resultCode) {
             Log.d(TAG, "powerResponse: token " + token + " resultCode " + resultCode);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
             Integer reqType = SapMessage.sOngoingRequests.remove(token);
             if (VERBOSE) {
-                Log.d(TAG, "powerResponse: reqType "
-                                + (reqType == null ? "null" : SapMessage.getMsgTypeName(reqType)));
+                Log.d(TAG, "powerResponse: reqType " + (reqType == null ? "null"
+                        : SapMessage.getMsgTypeName(reqType)));
             }
             SapMessage sapMessage;
             if (reqType == SapMessage.ID_POWER_SIM_OFF_REQ) {
@@ -146,6 +144,7 @@
             sendSapMessage(sapMessage);
         }
 
+        @Override
         public void resetSimResponse(int token, int resultCode) {
             Log.d(TAG, "resetSimResponse: token " + token + " resultCode " + resultCode);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
@@ -154,6 +153,7 @@
             removeOngoingReqAndSendMessage(token, sapMessage);
         }
 
+        @Override
         public void statusIndication(int token, int status) {
             Log.d(TAG, "statusIndication: token " + token + " status " + status);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
@@ -162,10 +162,12 @@
             sendSapMessage(sapMessage);
         }
 
-        public void transferCardReaderStatusResponse(
-                int token, int resultCode, int cardReaderStatus) {
-            Log.d(TAG, "transferCardReaderStatusResponse: token " + token + " resultCode "
-                            + resultCode + " cardReaderStatus " + cardReaderStatus);
+        @Override
+        public void transferCardReaderStatusResponse(int token, int resultCode,
+                int cardReaderStatus) {
+            Log.d(TAG,
+                    "transferCardReaderStatusResponse: token " + token + " resultCode " + resultCode
+                            + " cardReaderStatus " + cardReaderStatus);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
             SapMessage sapMessage = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_RESP);
             sapMessage.setResultCode(resultCode);
@@ -175,6 +177,7 @@
             removeOngoingReqAndSendMessage(token, sapMessage);
         }
 
+        @Override
         public void errorResponse(int token) {
             Log.d(TAG, "errorResponse: token " + token);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
@@ -184,6 +187,7 @@
             sendSapMessage(sapMessage);
         }
 
+        @Override
         public void transferProtocolResponse(int token, int resultCode) {
             Log.d(TAG, "transferProtocolResponse: token " + token + " resultCode " + resultCode);
             SapService.notifyUpdateWakeLock(mSapServiceHandler);
@@ -214,8 +218,8 @@
             try {
                 mSapProxy = ISap.getService(SERVICE_NAME_RIL_BT);
                 if (mSapProxy != null) {
-                    mSapProxy.linkToDeath(
-                            mSapProxyDeathRecipient, mSapProxyCookie.incrementAndGet());
+                    mSapProxy.linkToDeath(mSapProxyDeathRecipient,
+                            mSapProxyCookie.incrementAndGet());
                     mSapProxy.setCallback(mSapCallback);
                 } else {
                     Log.e(TAG, "getSapProxy: mSapProxy == null");
@@ -229,9 +233,8 @@
                 // if service is not up, treat it like death notification to try to get service
                 // again
                 mSapServerMsgHandler.sendMessageDelayed(
-                        mSapServerMsgHandler.obtainMessage(
-                                SapServer.SAP_PROXY_DEAD, mSapProxyCookie.get()),
-                        SapServer.ISAP_GET_SERVICE_DELAY_MILLIS);
+                        mSapServerMsgHandler.obtainMessage(SapServer.SAP_PROXY_DEAD,
+                                mSapProxyCookie.get()), SapServer.ISAP_GET_SERVICE_DELAY_MILLIS);
             }
             return mSapProxy;
         }
@@ -239,12 +242,20 @@
 
     public void resetSapProxy() {
         synchronized (mSapProxyLock) {
+            if (DEBUG) Log.d(TAG, "resetSapProxy :" + mSapProxy);
+            try {
+                if (mSapProxy != null) {
+                    mSapProxy.unlinkToDeath(mSapProxyDeathRecipient);
+                }
+            } catch (RemoteException | RuntimeException e) {
+                Log.e(TAG, "resetSapProxy: exception: " + e);
+            }
             mSapProxy = null;
         }
     }
 
-    public SapRilReceiver(Handler SapServerMsgHandler, Handler sapServiceHandler) {
-        mSapServerMsgHandler = SapServerMsgHandler;
+    public SapRilReceiver(Handler sapServerMsgHandler, Handler sapServiceHandler) {
+        mSapServerMsgHandler = sapServerMsgHandler;
         mSapServiceHandler = sapServiceHandler;
         mSapCallback = new SapCallback();
         mSapProxyDeathRecipient = new SapProxyDeathRecipient();
@@ -257,10 +268,14 @@
      * Notify SapServer that this class is ready for shutdown.
      */
     void notifyShutdown() {
-        if (DEBUG) Log.i(TAG, "notifyShutdown()");
+        if (DEBUG) {
+            Log.i(TAG, "notifyShutdown()");
+        }
         // If we are already shutdown, don't bother sending a notification.
         synchronized (mSapProxyLock) {
-            if (mSapProxy != null) sendShutdownMessage();
+            if (mSapProxy != null) {
+                sendShutdownMessage();
+            }
         }
     }
 
@@ -283,7 +298,7 @@
         do {
             countRead = is.read(buffer, offset, remaining);
 
-            if (countRead < 0 ) {
+            if (countRead < 0) {
                 Log.e(TAG, "Hit EOS reading message length");
                 return -1;
             }
@@ -292,20 +307,22 @@
             remaining -= countRead;
         } while (remaining > 0);
 
-        messageLength = ((buffer[0] & 0xff) << 24)
-                | ((buffer[1] & 0xff) << 16)
-                | ((buffer[2] & 0xff) << 8)
-                | (buffer[3] & 0xff);
-        if (VERBOSE) Log.e(TAG,"Message length found to be: "+messageLength);
+        messageLength =
+                ((buffer[0] & 0xff) << 24) | ((buffer[1] & 0xff) << 16) | ((buffer[2] & 0xff) << 8)
+                        | (buffer[3] & 0xff);
+        if (VERBOSE) {
+            Log.e(TAG, "Message length found to be: " + messageLength);
+        }
         // Read the message
         offset = 0;
         remaining = messageLength;
         do {
             countRead = is.read(buffer, offset, remaining);
 
-            if (countRead < 0 ) {
-                Log.e(TAG, "Hit EOS reading message.  messageLength=" + messageLength
-                        + " remaining=" + remaining);
+            if (countRead < 0) {
+                Log.e(TAG,
+                        "Hit EOS reading message.  messageLength=" + messageLength + " remaining="
+                                + remaining);
                 return -1;
             }
 
diff --git a/src/com/android/bluetooth/sap/SapServer.java b/src/com/android/bluetooth/sap/SapServer.java
index ef82535..0ea9575 100644
--- a/src/com/android/bluetooth/sap/SapServer.java
+++ b/src/com/android/bluetooth/sap/SapServer.java
@@ -5,6 +5,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothSap;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -49,9 +50,8 @@
     public static final boolean DEBUG = SapService.DEBUG;
     public static final boolean VERBOSE = SapService.VERBOSE;
 
-    private enum SAP_STATE    {
-        DISCONNECTED, CONNECTING, CONNECTING_CALL_ONGOING, CONNECTED,
-        CONNECTED_BUSY, DISCONNECTING;
+    private enum SAP_STATE {
+        DISCONNECTED, CONNECTING, CONNECTING_CALL_ONGOING, CONNECTED, CONNECTED_BUSY, DISCONNECTING;
     }
 
     private SAP_STATE mState = SAP_STATE.DISCONNECTED;
@@ -73,10 +73,10 @@
     private CountDownLatch mDeinitSignal = new CountDownLatch(1);
 
     /* Message ID's handled by the message handler */
-    public static final int SAP_MSG_RFC_REPLY =   0x00;
+    public static final int SAP_MSG_RFC_REPLY = 0x00;
     public static final int SAP_MSG_RIL_CONNECT = 0x01;
-    public static final int SAP_MSG_RIL_REQ =     0x02;
-    public static final int SAP_MSG_RIL_IND =     0x03;
+    public static final int SAP_MSG_RIL_REQ = 0x02;
+    public static final int SAP_MSG_RIL_IND = 0x03;
     public static final int SAP_RIL_SOCK_CLOSED = 0x04;
     public static final int SAP_PROXY_DEAD = 0x05;
 
@@ -89,7 +89,8 @@
     public static final int ISAP_GET_SERVICE_DELAY_MILLIS = 3 * 1000;
     private static final int DISCONNECT_TIMEOUT_IMMEDIATE = 5000; /* ms */
     private static final int DISCONNECT_TIMEOUT_RFCOMM = 2000; /* ms */
-    private PendingIntent pDiscIntent = null; // Holds a reference to disconnect timeout intents
+    private PendingIntent mPendingDiscIntent = null;
+    // Holds a reference to disconnect timeout intents
 
     /* We store the mMaxMessageSize, as we need a copy of it when the init. sequence completes */
     private int mMaxMsgSize = 0;
@@ -123,19 +124,24 @@
      * This handles the response from RIL.
      */
     private BroadcastReceiver mIntentReceiver;
+
     private class SapServerBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if(intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
-                if(VERBOSE) Log.i(TAG, "ACTION_PHONE_STATE_CHANGED intent received in state "
-                                        + mState.name()
-                                        + "PhoneState: "
-                                        + intent.getStringExtra(TelephonyManager.EXTRA_STATE));
-                if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
+            if (intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
+                if (VERBOSE) {
+                    Log.i(TAG,
+                            "ACTION_PHONE_STATE_CHANGED intent received in state " + mState.name()
+                                    + "PhoneState: " + intent.getStringExtra(
+                                    TelephonyManager.EXTRA_STATE));
+                }
+                if (mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
                     String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
-                    if(state != null) {
-                        if(state.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
-                            if(DEBUG) Log.i(TAG, "sending RIL.ACTION_RIL_RECONNECT_OFF_REQ intent");
+                    if (state != null) {
+                        if (state.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
+                            if (DEBUG) {
+                                Log.i(TAG, "sending RIL.ACTION_RIL_RECONNECT_OFF_REQ intent");
+                            }
                             SapMessage fakeConReq = new SapMessage(SapMessage.ID_CONNECT_REQ);
                             fakeConReq.setMaxMsgSize(mMaxMsgSize);
                             onConnectRequest(fakeConReq);
@@ -147,7 +153,7 @@
                         SapMessage.DISC_GRACEFULL);
                 Log.v(TAG, " - Received SAP_DISCONNECT_ACTION type: " + disconnectType);
 
-                if(disconnectType == SapMessage.DISC_RFCOMM) {
+                if (disconnectType == SapMessage.DISC_RFCOMM) {
                     // At timeout we need to close the RFCOMM socket to complete shutdown
                     shutdown();
                 } else if (mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.DISCONNECTING) {
@@ -167,28 +173,31 @@
      * @param testMode Use SapMessage.TEST_MODE_XXX
      */
     public void setTestMode(int testMode) {
-        if(SapMessage.TEST) {
+        if (SapMessage.TEST) {
             mTestMode = testMode;
         }
     }
 
     private void sendDisconnectInd(int discType) {
-        if(VERBOSE) Log.v(TAG, "in sendDisconnectInd()");
+        if (VERBOSE) {
+            Log.v(TAG, "in sendDisconnectInd()");
+        }
 
-        if(discType != SapMessage.DISC_FORCED){
-            if(VERBOSE) Log.d(TAG, "Sending  disconnect ("+discType+") indication to client");
+        if (discType != SapMessage.DISC_FORCED) {
+            if (VERBOSE) {
+                Log.d(TAG, "Sending  disconnect (" + discType + ") indication to client");
+            }
             /* Send disconnect to client */
             SapMessage discInd = new SapMessage(SapMessage.ID_DISCONNECT_IND);
             discInd.setDisconnectionType(discType);
             sendClientMessage(discInd);
 
             /* Handle local disconnect procedures */
-            if (discType == SapMessage.DISC_GRACEFULL)
-            {
+            if (discType == SapMessage.DISC_GRACEFULL) {
                 /* Update the notification to allow the user to initiate a force disconnect */
                 setNotification(SapMessage.DISC_IMMEDIATE, PendingIntent.FLAG_CANCEL_CURRENT);
 
-            } else if (discType == SapMessage.DISC_IMMEDIATE){
+            } else if (discType == SapMessage.DISC_IMMEDIATE) {
                 /* Request an immediate disconnect, but start a timer to force disconnect if the
                  * client do not obey our request. */
                 startDisconnectTimer(SapMessage.DISC_FORCED, DISCONNECT_TIMEOUT_IMMEDIATE);
@@ -207,8 +216,7 @@
         }
     }
 
-    void setNotification(int type, int flags)
-    {
+    void setNotification(int type, int flags) {
         String title, text, button, ticker;
         Notification notification;
         NotificationManager notificationManager =
@@ -218,13 +226,15 @@
                 NotificationManager.IMPORTANCE_HIGH);
         notificationManager.createNotificationChannel(notificationChannel);
         flags |= PendingIntent.FLAG_IMMUTABLE;
-        if(VERBOSE) Log.i(TAG, "setNotification type: " + type);
+        if (VERBOSE) {
+            Log.i(TAG, "setNotification type: " + type);
+        }
         /* For PTS TC_SERVER_DCN_BV_03_I we need to expose the option to send immediate disconnect
          * without first sending a graceful disconnect.
          * To enable this option set
          * bt.sap.pts="true" */
-        String pts_enabled = SystemProperties.get("bt.sap.pts");
-        Boolean pts_test = Boolean.parseBoolean(pts_enabled);
+        String ptsEnabled = SystemProperties.get("bt.sap.pts");
+        Boolean ptsTest = Boolean.parseBoolean(ptsEnabled);
 
         /* put notification up for the user to be able to disconnect from the client*/
         Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
@@ -239,35 +249,37 @@
             text = mContext.getString(R.string.bluetooth_sap_notif_disconnecting);
             ticker = mContext.getString(R.string.bluetooth_sap_notif_ticker);
         }
-        if (!pts_test) {
+        if (!ptsTest) {
             sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, type);
-            PendingIntent pIntentDisconnect = PendingIntent.getBroadcast(mContext, type,
-                    sapDisconnectIntent,flags);
-            notification = new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL)
-                                   .setOngoing(true)
-                                   .addAction(android.R.drawable.stat_sys_data_bluetooth, button,
-                                           pIntentDisconnect)
-                                   .setContentTitle(title)
-                                   .setTicker(ticker)
-                                   .setContentText(text)
-                                   .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
-                                   .setAutoCancel(false)
-                                   .setPriority(Notification.PRIORITY_MAX)
-                                   .setOnlyAlertOnce(true)
-                                   .build();
+            PendingIntent pIntentDisconnect =
+                    PendingIntent.getBroadcast(mContext, type, sapDisconnectIntent, flags);
+            notification =
+                    new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL).setOngoing(true)
+                            .addAction(android.R.drawable.stat_sys_data_bluetooth, button,
+                                    pIntentDisconnect)
+                            .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();
         } else {
             sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA,
                     SapMessage.DISC_GRACEFULL);
             Intent sapForceDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
             sapForceDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA,
                     SapMessage.DISC_IMMEDIATE);
-            PendingIntent pIntentDisconnect = PendingIntent.getBroadcast(mContext,
-                    SapMessage.DISC_GRACEFULL, sapDisconnectIntent,flags);
-            PendingIntent pIntentForceDisconnect = PendingIntent.getBroadcast(mContext,
-                    SapMessage.DISC_IMMEDIATE, sapForceDisconnectIntent,flags);
+            PendingIntent pIntentDisconnect =
+                    PendingIntent.getBroadcast(mContext, SapMessage.DISC_GRACEFULL,
+                            sapDisconnectIntent, flags);
+            PendingIntent pIntentForceDisconnect =
+                    PendingIntent.getBroadcast(mContext, SapMessage.DISC_IMMEDIATE,
+                            sapForceDisconnectIntent, flags);
             notification =
-                    new Notification.Builder(mContext, SAP_NOTIFICATION_CHANNEL)
-                            .setOngoing(true)
+                    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),
@@ -283,11 +295,12 @@
                             .setAutoCancel(false)
                             .setPriority(Notification.PRIORITY_MAX)
                             .setOnlyAlertOnce(true)
+                            .setLocalOnly(true)
                             .build();
         }
 
         // cannot be set with the builder
-        notification.flags |= Notification.FLAG_NO_CLEAR |Notification.FLAG_ONLY_ALERT_ONCE;
+        notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONLY_ALERT_ONCE;
 
         notificationManager.notify(NOTIFICATION_ID, notification);
     }
@@ -321,27 +334,35 @@
             mRilBtReceiver = new SapRilReceiver(mSapHandler, mSapServiceHandler);
             boolean done = false;
             while (!done) {
-                if(VERBOSE) Log.i(TAG, "Waiting for incomming RFCOMM message...");
+                if (VERBOSE) {
+                    Log.i(TAG, "Waiting for incomming RFCOMM message...");
+                }
                 int requestType = mRfcommIn.read();
-                if (VERBOSE) Log.i(TAG, "RFCOMM message read...");
-                if(requestType == -1) {
-                    if (VERBOSE) Log.i(TAG, "requestType == -1");
+                if (VERBOSE) {
+                    Log.i(TAG, "RFCOMM message read...");
+                }
+                if (requestType == -1) {
+                    if (VERBOSE) {
+                        Log.i(TAG, "requestType == -1");
+                    }
                     done = true; // EOF reached
                 } else {
-                    if (VERBOSE) Log.i(TAG, "requestType != -1");
+                    if (VERBOSE) {
+                        Log.i(TAG, "requestType != -1");
+                    }
                     SapMessage msg = SapMessage.readMessage(requestType, mRfcommIn);
                     /* notify about an incoming message from the BT Client */
                     SapService.notifyUpdateWakeLock(mSapServiceHandler);
-                    if(msg != null && mState != SAP_STATE.DISCONNECTING)
-                    {
+                    if (msg != null && mState != SAP_STATE.DISCONNECTING) {
                         switch (requestType) {
-                        case SapMessage.ID_CONNECT_REQ:
-                            if(VERBOSE) Log.d(TAG, "CONNECT_REQ - MaxMsgSize: "
-                                    + msg.getMaxMsgSize());
-                            onConnectRequest(msg);
-                            msg = null; /* don't send ril connect yet */
-                            break;
-                        case SapMessage.ID_DISCONNECT_REQ: /* No params */
+                            case SapMessage.ID_CONNECT_REQ:
+                                if (VERBOSE) {
+                                    Log.d(TAG, "CONNECT_REQ - MaxMsgSize: " + msg.getMaxMsgSize());
+                                }
+                                onConnectRequest(msg);
+                                msg = null; /* don't send ril connect yet */
+                                break;
+                            case SapMessage.ID_DISCONNECT_REQ: /* No params */
                             /*
                              * 1) send RIL_REQUEST_SIM_SAP_DISCONNECT
                              *      (block for all incoming requests, as they are not
@@ -355,57 +376,63 @@
                              *       cancel timer and initiate cleanup
                              * 6.b) on rfcomm disc. timeout:
                              *       close socket-streams and initiate cleanup */
-                            if(VERBOSE) Log.d(TAG, "DISCONNECT_REQ");
+                                if (VERBOSE) {
+                                    Log.d(TAG, "DISCONNECT_REQ");
+                                }
 
-                            if (mState ==  SAP_STATE.CONNECTING_CALL_ONGOING) {
-                                Log.d(TAG, "disconnect received when call was ongoing, " +
-                                     "send disconnect response");
-                                changeState(SAP_STATE.DISCONNECTING);
-                                SapMessage reply = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
-                                sendClientMessage(reply);
-                            } else {
-                                clearPendingRilResponses(msg);
-                                changeState(SAP_STATE.DISCONNECTING);
-                                sendRilThreadMessage(msg);
+                                if (mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
+                                    Log.d(TAG, "disconnect received when call was ongoing, "
+                                            + "send disconnect response");
+                                    changeState(SAP_STATE.DISCONNECTING);
+                                    SapMessage reply =
+                                            new SapMessage(SapMessage.ID_DISCONNECT_RESP);
+                                    sendClientMessage(reply);
+                                } else {
+                                    clearPendingRilResponses(msg);
+                                    changeState(SAP_STATE.DISCONNECTING);
+                                    sendRilThreadMessage(msg);
                                 /*cancel the timer for the hard-disconnect intent*/
-                                stopDisconnectTimer();
-                            }
-                            msg = null; // No message needs to be sent to RIL
-                            break;
-                        case SapMessage.ID_POWER_SIM_OFF_REQ: // Fall through
-                        case SapMessage.ID_RESET_SIM_REQ:
+                                    stopDisconnectTimer();
+                                }
+                                msg = null; // No message needs to be sent to RIL
+                                break;
+                            case SapMessage.ID_POWER_SIM_OFF_REQ: // Fall through
+                            case SapMessage.ID_RESET_SIM_REQ:
                             /* Forward these to the RIL regardless of the state, and clear any
                              * pending resp */
-                            clearPendingRilResponses(msg);
-                            break;
-                        case SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ:
+                                clearPendingRilResponses(msg);
+                                break;
+                            case SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ:
                             /* The RIL might support more protocols that specified in the SAP,
                              * allow only the valid values. */
-                            if(mState == SAP_STATE.CONNECTED
-                                    && msg.getTransportProtocol() != 0
-                                    && msg.getTransportProtocol() != 1) {
-                                Log.w(TAG, "Invalid TransportProtocol received:"
-                                        + msg.getTransportProtocol());
-                                // We shall only handle one request at the time, hence return error
-                                SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
-                                sendClientMessage(errorReply);
-                                msg = null;
-                            }
-                            // Fall through
-                        default:
+                                if (mState == SAP_STATE.CONNECTED && msg.getTransportProtocol() != 0
+                                        && msg.getTransportProtocol() != 1) {
+                                    Log.w(TAG, "Invalid TransportProtocol received:"
+                                            + msg.getTransportProtocol());
+                                    // We shall only handle one request at the time, hence return
+                                    // error
+                                    SapMessage errorReply =
+                                            new SapMessage(SapMessage.ID_ERROR_RESP);
+                                    sendClientMessage(errorReply);
+                                    msg = null;
+                                }
+                                // Fall through
+                            default:
                             /* Remaining cases just needs to be forwarded to the RIL unless we are
                              * in busy state. */
-                            if(mState != SAP_STATE.CONNECTED) {
-                                Log.w(TAG, "Message received in STATE != CONNECTED - state = "
-                                        + mState.name());
-                                // We shall only handle one request at the time, hence return error
-                                SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
-                                sendClientMessage(errorReply);
-                                msg = null;
-                            }
+                                if (mState != SAP_STATE.CONNECTED) {
+                                    Log.w(TAG, "Message received in STATE != CONNECTED - state = "
+                                            + mState.name());
+                                    // We shall only handle one request at the time, hence return
+                                    // error
+                                    SapMessage errorReply =
+                                            new SapMessage(SapMessage.ID_ERROR_RESP);
+                                    sendClientMessage(errorReply);
+                                    msg = null;
+                                }
                         }
 
-                        if(msg != null && msg.getSendToRil() == true) {
+                        if (msg != null && msg.getSendToRil()) {
                             changeState(SAP_STATE.CONNECTED_BUSY);
                             sendRilThreadMessage(msg);
                         }
@@ -427,6 +454,12 @@
             /* TODO: Change to the needed Exception types when done testing */
             Log.w(TAG, e);
         } finally {
+            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            int state = (adapter != null) ? adapter.getState() : -1;
+            if (state != BluetoothAdapter.STATE_ON) {
+                if (DEBUG) Log.d(TAG, "BT State :" + state);
+                mDeinitSignal.countDown();
+            }
             // Do cleanup even if an exception occurs
             stopDisconnectTimer();
             /* In case of e.g. a RFCOMM close while connected:
@@ -437,11 +470,12 @@
                 /* Most likely remote device closed rfcomm, update state */
                 changeState(SAP_STATE.DISCONNECTED);
             } else if (mState != SAP_STATE.DISCONNECTED) {
-                if(mState != SAP_STATE.DISCONNECTING &&
-                        mIsLocalInitDisconnect != true) {
+                if (mState != SAP_STATE.DISCONNECTING && !mIsLocalInitDisconnect) {
                     sendDisconnectInd(SapMessage.DISC_FORCED);
                 }
-                if(DEBUG) Log.i(TAG, "Waiting for deinit to complete");
+                if (DEBUG) {
+                    Log.i(TAG, "Waiting for deinit to complete");
+                }
                 try {
                     mDeinitSignal.await();
                 } catch (InterruptedException e) {
@@ -449,40 +483,55 @@
                 }
             }
 
-            if(mIntentReceiver != null) {
+            if (mIntentReceiver != null) {
                 mContext.unregisterReceiver(mIntentReceiver);
                 mIntentReceiver = null;
             }
             stopDisconnectTimer();
             clearNotification();
 
-            if(mHandlerThread != null) try {
-                mHandlerThread.quit();
-                mHandlerThread.join();
-                mHandlerThread = null;
-            } catch (InterruptedException e) {}
+            if (mHandlerThread != null) {
+                try {
+                    mHandlerThread.quit();
+                    mHandlerThread.join();
+                    mHandlerThread = null;
+                } catch (InterruptedException e) {
+                }
+            }
             if (mRilBtReceiver != null) {
                 mRilBtReceiver.resetSapProxy();
                 mRilBtReceiver = null;
             }
 
-            if(mRfcommIn != null) try {
-                if(VERBOSE) Log.i(TAG, "Closing mRfcommIn...");
-                mRfcommIn.close();
-                mRfcommIn = null;
-            } catch (IOException e) {}
+            if (mRfcommIn != null) {
+                try {
+                    if (VERBOSE) {
+                        Log.i(TAG, "Closing mRfcommIn...");
+                    }
+                    mRfcommIn.close();
+                    mRfcommIn = null;
+                } catch (IOException e) {
+                }
+            }
 
-            if(mRfcommOut != null) try {
-                if(VERBOSE) Log.i(TAG, "Closing mRfcommOut...");
-                mRfcommOut.close();
-                mRfcommOut = null;
-            } catch (IOException e) {}
+            if (mRfcommOut != null) {
+                try {
+                    if (VERBOSE) {
+                        Log.i(TAG, "Closing mRfcommOut...");
+                    }
+                    mRfcommOut.close();
+                    mRfcommOut = null;
+                } catch (IOException e) {
+                }
+            }
 
             if (mSapServiceHandler != null) {
                 Message msg = Message.obtain(mSapServiceHandler);
                 msg.what = SapService.MSG_SERVERSESSION_CLOSE;
                 msg.sendToTarget();
-                if (DEBUG) Log.d(TAG, "MSG_SERVERSESSION_CLOSE sent out.");
+                if (DEBUG) {
+                    Log.d(TAG, "MSG_SERVERSESSION_CLOSE sent out.");
+                }
             }
             Log.i(TAG, "All done exiting thread...");
         }
@@ -502,7 +551,7 @@
     private void onConnectRequest(SapMessage msg) {
         SapMessage reply = new SapMessage(SapMessage.ID_CONNECT_RESP);
 
-        if(mState == SAP_STATE.CONNECTING) {
+        if (mState == SAP_STATE.CONNECTING) {
             /* A connect request might have been rejected because of maxMessageSize negotiation, and
              * this is a new connect request. Simply forward to RIL, and stay in connecting state.
              * */
@@ -510,13 +559,14 @@
             sendRilMessage(msg);
             stopDisconnectTimer();
 
-        } else if(mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.CONNECTING_CALL_ONGOING) {
+        } else if (mState != SAP_STATE.DISCONNECTED
+                && mState != SAP_STATE.CONNECTING_CALL_ONGOING) {
             reply.setConnectionStatus(SapMessage.CON_STATUS_ERROR_CONNECTION);
         } else {
             // Store the MaxMsgSize for future use
             mMaxMsgSize = msg.getMaxMsgSize();
             // All parameters OK, examine if a call is ongoing and start the RIL-BT listener thread
-            if (isCallOngoing() == true) {
+            if (isCallOngoing()) {
                 /* If a call is ongoing we set the state, inform the SAP client and wait for a state
                  * change intent from the TelephonyManager with state IDLE. */
                 reply.setConnectionStatus(SapMessage.CON_STATUS_OK_ONGOING_CALL);
@@ -538,15 +588,17 @@
                 }
             }
         }
-        if(reply != null)
+        if (reply != null) {
             sendClientMessage(reply);
+        }
     }
 
     private void clearPendingRilResponses(SapMessage msg) {
-        if(mState == SAP_STATE.CONNECTED_BUSY) {
+        if (mState == SAP_STATE.CONNECTED_BUSY) {
             msg.setClearRilQueue(true);
         }
     }
+
     /**
      * Send RFCOMM message to the Sap Server Handler Thread
      * @param sapMsg The message to send
@@ -571,8 +623,8 @@
      */
     private boolean isCallOngoing() {
         TelephonyManager tManager =
-                (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        if(tManager.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        if (tManager.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
             return false;
         }
         return true;
@@ -584,8 +636,9 @@
      * @param newState
      */
     private void changeState(SAP_STATE newState) {
-        if(DEBUG) Log.i(TAG_HANDLER,"Changing state from " + mState.name() +
-                                        " to " + newState.name());
+        if (DEBUG) {
+            Log.i(TAG_HANDLER, "Changing state from " + mState.name() + " to " + newState.name());
+        }
         synchronized (this) {
             mState = newState;
         }
@@ -604,56 +657,60 @@
      */
     @Override
     public boolean handleMessage(Message msg) {
-        if(VERBOSE) Log.i(TAG_HANDLER,"Handling message (ID: " + msg.what + "): "
-                + getMessageName(msg.what));
+        if (VERBOSE) {
+            Log.i(TAG_HANDLER,
+                    "Handling message (ID: " + msg.what + "): " + getMessageName(msg.what));
+        }
 
         SapMessage sapMsg = null;
 
-        switch(msg.what) {
-        case SAP_MSG_RFC_REPLY:
-            sapMsg = (SapMessage) msg.obj;
-            handleRfcommReply(sapMsg);
-            break;
-        case SAP_MSG_RIL_CONNECT:
+        switch (msg.what) {
+            case SAP_MSG_RFC_REPLY:
+                sapMsg = (SapMessage) msg.obj;
+                handleRfcommReply(sapMsg);
+                break;
+            case SAP_MSG_RIL_CONNECT:
             /* The connection to rild-bt have been established. Store the outStream handle
              * and send the connect request. */
-            if(mTestMode != SapMessage.INVALID_VALUE) {
-                SapMessage rilTestModeReq = new SapMessage(SapMessage.ID_RIL_SIM_ACCESS_TEST_REQ);
-                rilTestModeReq.setTestMode(mTestMode);
-                sendRilMessage(rilTestModeReq);
-                mTestMode = SapMessage.INVALID_VALUE;
-            }
-            SapMessage rilSapConnect = new SapMessage(SapMessage.ID_CONNECT_REQ);
-            rilSapConnect.setMaxMsgSize(mMaxMsgSize);
-            sendRilMessage(rilSapConnect);
-            break;
-        case SAP_MSG_RIL_REQ:
-            sapMsg = (SapMessage) msg.obj;
-            if(sapMsg != null) {
-                sendRilMessage(sapMsg);
-            }
-            break;
-        case SAP_MSG_RIL_IND:
-            sapMsg = (SapMessage) msg.obj;
-            handleRilInd(sapMsg);
-            break;
-        case SAP_RIL_SOCK_CLOSED:
+                if (mTestMode != SapMessage.INVALID_VALUE) {
+                    SapMessage rilTestModeReq =
+                            new SapMessage(SapMessage.ID_RIL_SIM_ACCESS_TEST_REQ);
+                    rilTestModeReq.setTestMode(mTestMode);
+                    sendRilMessage(rilTestModeReq);
+                    mTestMode = SapMessage.INVALID_VALUE;
+                }
+                SapMessage rilSapConnect = new SapMessage(SapMessage.ID_CONNECT_REQ);
+                rilSapConnect.setMaxMsgSize(mMaxMsgSize);
+                sendRilMessage(rilSapConnect);
+                break;
+            case SAP_MSG_RIL_REQ:
+                sapMsg = (SapMessage) msg.obj;
+                if (sapMsg != null) {
+                    sendRilMessage(sapMsg);
+                }
+                break;
+            case SAP_MSG_RIL_IND:
+                sapMsg = (SapMessage) msg.obj;
+                handleRilInd(sapMsg);
+                break;
+            case SAP_RIL_SOCK_CLOSED:
             /* The RIL socket was closed unexpectedly, send immediate disconnect indication
                - close RFCOMM after timeout if no response. */
-            sendDisconnectInd(SapMessage.DISC_IMMEDIATE);
-            startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
-            break;
-        case SAP_PROXY_DEAD:
-            if ((long) msg.obj == mRilBtReceiver.mSapProxyCookie.get()) {
-                mRilBtReceiver.notifyShutdown(); /* Only needed in case of a connection error */
-                mRilBtReceiver.resetSapProxy();
+                startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
+                break;
+            case SAP_PROXY_DEAD:
+                if ((long) msg.obj == mRilBtReceiver.mSapProxyCookie.get()) {
+                    mRilBtReceiver.notifyShutdown(); /* Only needed in case of a connection error */
+                    mRilBtReceiver.resetSapProxy();
 
-                // todo: rild should be back up since message was sent with a delay. this is a hack.
-                mRilBtReceiver.getSapProxy();
-            }
-        default:
+                    // todo: rild should be back up since message was sent with a delay. this is
+                    // a hack.
+                    mRilBtReceiver.getSapProxy();
+                }
+                break;
+            default:
             /* Message not handled */
-            return false;
+                return false;
         }
         return true; // Message handles
     }
@@ -664,15 +721,21 @@
      */
     private void shutdown() {
 
-        if(DEBUG) Log.i(TAG_HANDLER, "in Shutdown()");
+        if (DEBUG) {
+            Log.i(TAG_HANDLER, "in Shutdown()");
+        }
         try {
-            if (mRfcommOut != null)
+            if (mRfcommOut != null) {
                 mRfcommOut.close();
-        } catch (IOException e) {}
+            }
+        } catch (IOException e) {
+        }
         try {
-            if (mRfcommIn != null)
+            if (mRfcommIn != null) {
                 mRfcommIn.close();
-        } catch (IOException e) {}
+            }
+        } catch (IOException e) {
+        }
         mRfcommIn = null;
         mRfcommOut = null;
         stopDisconnectTimer();
@@ -687,30 +750,30 @@
             sapDisconnectIntent.putExtra(SAP_DISCONNECT_TYPE_EXTRA, discType);
             AlarmManager alarmManager =
                     (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-            pDiscIntent = PendingIntent.getBroadcast(mContext,
-                                                    discType,
-                                                    sapDisconnectIntent,
-                                                    PendingIntent.FLAG_CANCEL_CURRENT);
+            mPendingDiscIntent = PendingIntent.getBroadcast(mContext, discType, sapDisconnectIntent,
+                    PendingIntent.FLAG_CANCEL_CURRENT);
             alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                    SystemClock.elapsedRealtime() + timeMs, pDiscIntent);
+                    SystemClock.elapsedRealtime() + timeMs, mPendingDiscIntent);
 
-            if(VERBOSE) Log.d(TAG_HANDLER, "Setting alarm for " + timeMs +
-                    " ms to activate disconnect type " + discType);
+            if (VERBOSE) {
+                Log.d(TAG_HANDLER,
+                        "Setting alarm for " + timeMs + " ms to activate disconnect type "
+                                + discType);
+            }
         }
     }
 
     private void stopDisconnectTimer() {
         synchronized (this) {
-            if(pDiscIntent != null)
-            {
+            if (mPendingDiscIntent != null) {
                 AlarmManager alarmManager =
                         (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-                alarmManager.cancel(pDiscIntent);
-                pDiscIntent.cancel();
-                if(VERBOSE) {
+                alarmManager.cancel(mPendingDiscIntent);
+                mPendingDiscIntent.cancel();
+                if (VERBOSE) {
                     Log.d(TAG_HANDLER, "Canceling disconnect alarm");
                 }
-                pDiscIntent = null;
+                mPendingDiscIntent = null;
             }
         }
     }
@@ -722,15 +785,17 @@
      * @param sapMsg the message to send to the SAP client
      */
     private void handleRfcommReply(SapMessage sapMsg) {
-        if(sapMsg != null) {
+        if (sapMsg != null) {
 
-            if(DEBUG) Log.i(TAG_HANDLER, "handleRfcommReply() handling "
-                    + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
+            if (DEBUG) {
+                Log.i(TAG_HANDLER, "handleRfcommReply() handling " + SapMessage.getMsgTypeName(
+                        sapMsg.getMsgType()));
+            }
 
-            switch(sapMsg.getMsgType()) {
+            switch (sapMsg.getMsgType()) {
 
                 case SapMessage.ID_CONNECT_RESP:
-                    if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
+                    if (mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
                         /* Hold back the connect resp if a call was ongoing when the connect req
                          * was received.
                          * A response with status call-ongoing was sent, and the connect response
@@ -740,16 +805,18 @@
                             // This is successful connect response from RIL/modem.
                             changeState(SAP_STATE.CONNECTED);
                         }
-                        if(VERBOSE) Log.i(TAG, "Hold back the connect resp, as a call was ongoing" +
-                                " when the initial response were sent.");
+                        if (VERBOSE) {
+                            Log.i(TAG, "Hold back the connect resp, as a call was ongoing"
+                                    + " when the initial response were sent.");
+                        }
                         sapMsg = null;
                     } else if (sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK) {
                         // This is successful connect response from RIL/modem.
                         changeState(SAP_STATE.CONNECTED);
-                    } else if(sapMsg.getConnectionStatus() ==
-                            SapMessage.CON_STATUS_OK_ONGOING_CALL) {
+                    } else if (sapMsg.getConnectionStatus()
+                            == SapMessage.CON_STATUS_OK_ONGOING_CALL) {
                         changeState(SAP_STATE.CONNECTING_CALL_ONGOING);
-                    } else if(sapMsg.getConnectionStatus() != SapMessage.CON_STATUS_OK) {
+                    } else if (sapMsg.getConnectionStatus() != SapMessage.CON_STATUS_OK) {
                         /* Most likely the peer will try to connect again, hence we keep the
                          * connection to RIL open and stay in connecting state.
                          *
@@ -759,11 +826,13 @@
                     }
                     break;
                 case SapMessage.ID_DISCONNECT_RESP:
-                    if(mState == SAP_STATE.DISCONNECTING) {
+                    if (mState == SAP_STATE.DISCONNECTING) {
                         /* Close the RIL-BT output Stream and signal to SapRilReceiver to close
                          * down the input stream. */
-                        if(DEBUG) Log.i(TAG, "ID_DISCONNECT_RESP received in SAP_STATE." +
-                                "DISCONNECTING.");
+                        if (DEBUG) {
+                            Log.i(TAG,
+                                    "ID_DISCONNECT_RESP received in SAP_STATE." + "DISCONNECTING.");
+                        }
 
                         /* Send the disconnect resp, and wait for the client to close the Rfcomm,
                          * but start a timeout timer, just to be sure. Use alarm, to ensure we wake
@@ -775,8 +844,10 @@
                         mDeinitSignal.countDown(); /* Signal deinit complete */
                     } else { /* DISCONNECTED */
                         mDeinitSignal.countDown(); /* Signal deinit complete */
-                        if(mIsLocalInitDisconnect == true) {
-                            if(VERBOSE) Log.i(TAG_HANDLER, "This is a FORCED disconnect.");
+                        if (mIsLocalInitDisconnect) {
+                            if (VERBOSE) {
+                                Log.i(TAG_HANDLER, "This is a FORCED disconnect.");
+                            }
                             /* We needed to force the disconnect, hence no hope for the client to
                              * close the RFCOMM connection, hence we do it here. */
                             shutdown();
@@ -786,7 +857,9 @@
                              * need to do it.
                              * We start an alarm, and if it triggers, we must send the
                              * MSG_SERVERSESSION_CLOSE */
-                            if(VERBOSE) Log.i(TAG_HANDLER, "This is a NORMAL disconnect.");
+                            if (VERBOSE) {
+                                Log.i(TAG_HANDLER, "This is a NORMAL disconnect.");
+                            }
                             startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
                         }
                     }
@@ -794,9 +867,8 @@
                 case SapMessage.ID_STATUS_IND:
                     /* Some car-kits only "likes" status indication when connected, hence discard
                      * any arriving outside this state */
-                    if(mState == SAP_STATE.DISCONNECTED ||
-                            mState == SAP_STATE.CONNECTING ||
-                            mState == SAP_STATE.DISCONNECTING) {
+                    if (mState == SAP_STATE.DISCONNECTED || mState == SAP_STATE.CONNECTING
+                            || mState == SAP_STATE.DISCONNECTING) {
                         sapMsg = null;
                     }
                     if (mSapServiceHandler != null && mState == SAP_STATE.CONNECTED) {
@@ -805,11 +877,13 @@
                         msg.arg1 = BluetoothSap.STATE_CONNECTED;
                         msg.sendToTarget();
                         setNotification(SapMessage.DISC_GRACEFULL, 0);
-                        if (DEBUG) Log.d(TAG, "MSG_CHANGE_STATE sent out.");
+                        if (DEBUG) {
+                            Log.d(TAG, "MSG_CHANGE_STATE sent out.");
+                        }
                     }
                     break;
                 default:
-                // Nothing special, just send the message
+                    // Nothing special, just send the message
             }
         }
 
@@ -817,39 +891,42 @@
          * handle one request at the time, except from disconnect, sim off and sim reset.
          * Hence if one of these are received while in busy state, we might have a crossing
          * response, hence we must stay in BUSY state if we have an ongoing RIL request. */
-        if(mState == SAP_STATE.CONNECTED_BUSY) {
-            if(SapMessage.getNumPendingRilMessages() == 0) {
+        if (mState == SAP_STATE.CONNECTED_BUSY) {
+            if (SapMessage.getNumPendingRilMessages() == 0) {
                 changeState(SAP_STATE.CONNECTED);
             }
         }
 
         // This is the default case - just send the message to the SAP client.
-        if(sapMsg != null)
+        if (sapMsg != null) {
             sendReply(sapMsg);
+        }
     }
 
     private void handleRilInd(SapMessage sapMsg) {
-        if(sapMsg == null)
+        if (sapMsg == null) {
             return;
-
-        switch(sapMsg.getMsgType()) {
-        case SapMessage.ID_DISCONNECT_IND:
-        {
-            if(mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.DISCONNECTING){
-                /* we only send disconnect indication to the client if we are actually connected*/
-                SapMessage reply = new SapMessage(SapMessage.ID_DISCONNECT_IND);
-                reply.setDisconnectionType(sapMsg.getDisconnectionType()) ;
-                sendClientMessage(reply);
-            } else {
-                /* TODO: This was introduced to handle disconnect indication from RIL */
-                sendDisconnectInd(sapMsg.getDisconnectionType());
-            }
-            break;
         }
 
-        default:
-            if(DEBUG) Log.w(TAG_HANDLER,"Unhandled message - type: "
-                    + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
+        switch (sapMsg.getMsgType()) {
+            case SapMessage.ID_RIL_UNSOL_DISCONNECT_IND: {
+                if (mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.DISCONNECTING) {
+                /* we only send disconnect indication to the client if we are actually connected*/
+                    SapMessage reply = new SapMessage(SapMessage.ID_DISCONNECT_IND);
+                    reply.setDisconnectionType(sapMsg.getDisconnectionType());
+                    sendClientMessage(reply);
+                } else {
+                /* TODO: This was introduced to handle disconnect indication from RIL */
+                    sendDisconnectInd(sapMsg.getDisconnectionType());
+                }
+                break;
+            }
+
+            default:
+                if (DEBUG) {
+                    Log.w(TAG_HANDLER, "Unhandled message - type: " + SapMessage.getMsgTypeName(
+                            sapMsg.getMsgType()));
+                }
         }
     }
 
@@ -858,8 +935,10 @@
      * @param sapMsg
      */
     private void sendRilMessage(SapMessage sapMsg) {
-        if(VERBOSE) Log.i(TAG_HANDLER, "sendRilMessage() - "
-                + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
+        if (VERBOSE) {
+            Log.i(TAG_HANDLER,
+                    "sendRilMessage() - " + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
+        }
 
         Log.d(TAG_HANDLER, "sendRilMessage: calling getSapProxy");
         synchronized (mRilBtReceiver.getSapProxyLock()) {
@@ -892,9 +971,11 @@
      * Only call this from the sapHandler thread.
      */
     private void sendReply(SapMessage msg) {
-        if(VERBOSE) Log.i(TAG_HANDLER, "sendReply() RFCOMM - "
-                + SapMessage.getMsgTypeName(msg.getMsgType()));
-        if(mRfcommOut != null) { // Needed to handle brutal shutdown from car-kit and out of range
+        if (VERBOSE) {
+            Log.i(TAG_HANDLER,
+                    "sendReply() RFCOMM - " + SapMessage.getMsgTypeName(msg.getMsgType()));
+        }
+        if (mRfcommOut != null) { // Needed to handle brutal shutdown from car-kit and out of range
             try {
                 msg.write(mRfcommOut);
                 mRfcommOut.flush();
@@ -908,16 +989,16 @@
 
     private static String getMessageName(int messageId) {
         switch (messageId) {
-        case SAP_MSG_RFC_REPLY:
-            return "SAP_MSG_REPLY";
-        case SAP_MSG_RIL_CONNECT:
-            return "SAP_MSG_RIL_CONNECT";
-        case SAP_MSG_RIL_REQ:
-            return "SAP_MSG_RIL_REQ";
-        case SAP_MSG_RIL_IND:
-            return "SAP_MSG_RIL_IND";
-        default:
-            return "Unknown message ID";
+            case SAP_MSG_RFC_REPLY:
+                return "SAP_MSG_REPLY";
+            case SAP_MSG_RIL_CONNECT:
+                return "SAP_MSG_RIL_CONNECT";
+            case SAP_MSG_RIL_REQ:
+                return "SAP_MSG_RIL_REQ";
+            case SAP_MSG_RIL_IND:
+                return "SAP_MSG_RIL_IND";
+            default:
+                return "Unknown message ID";
         }
     }
 
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index 3804d64..66e0b19 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -1,10 +1,5 @@
 package com.android.bluetooth.sap;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
 import android.annotation.TargetApi;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -26,16 +21,22 @@
 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.btservice.ProfileService.IProfileServiceBinder;
 import com.android.bluetooth.sdp.SdpManager;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
 @TargetApi(Build.VERSION_CODES.ECLAIR)
 public class SapService extends ProfileService {
 
@@ -91,8 +92,10 @@
     private boolean mIsWaitingAuthorization = false;
     private boolean mIsRegistered = false;
 
+    private static SapService sSapService;
+
     private static final ParcelUuid[] SAP_UUIDS = {
-        BluetoothUuid.SAP,
+            BluetoothUuid.SAP,
     };
 
 
@@ -115,16 +118,19 @@
     }
 
     private void removeSdpRecord() {
-        if (mAdapter != null && mSdpHandle >= 0 &&
-                                SdpManager.getDefaultManager() != null) {
-            if(VERBOSE) Log.d(TAG, "Removing SDP record handle: " + mSdpHandle);
+        if (mAdapter != null && mSdpHandle >= 0 && SdpManager.getDefaultManager() != null) {
+            if (VERBOSE) {
+                Log.d(TAG, "Removing SDP record handle: " + mSdpHandle);
+            }
             boolean status = SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle);
             mSdpHandle = -1;
         }
     }
 
     private void startRfcommSocketListener() {
-        if (VERBOSE) Log.v(TAG, "Sap Service startRfcommSocketListener");
+        if (VERBOSE) {
+            Log.v(TAG, "Sap Service startRfcommSocketListener");
+        }
 
         if (mAcceptThread == null) {
             mAcceptThread = new SocketAcceptThread();
@@ -133,11 +139,14 @@
         }
     }
 
-    private final boolean initSocket() {
-        if (VERBOSE) Log.v(TAG, "Sap Service initSocket");
+    private static final int CREATE_RETRY_TIME = 10;
+
+    private boolean initSocket() {
+        if (VERBOSE) {
+            Log.v(TAG, "Sap Service initSocket");
+        }
 
         boolean initSocketOK = false;
-        final int CREATE_RETRY_TIME = 10;
 
         // It's possible that create will fail in some cases. retry for 10 times
         for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) {
@@ -149,8 +158,9 @@
                 mServerSocket = mAdapter.listenUsingRfcommOn(
                         BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true, true);
                 removeSdpRecord();
-                mSdpHandle = SdpManager.getDefaultManager().createSapsRecord(SDP_SAP_SERVICE_NAME,
-                        mServerSocket.getChannel(), SDP_SAP_VERSION);
+                mSdpHandle = SdpManager.getDefaultManager()
+                        .createSapsRecord(SDP_SAP_SERVICE_NAME, mServerSocket.getChannel(),
+                                SDP_SAP_VERSION);
             } catch (IOException e) {
                 Log.e(TAG, "Error create RfcommServerSocket ", e);
                 initSocketOK = false;
@@ -158,15 +168,19 @@
 
             if (!initSocketOK) {
                 // Need to break out of this loop if BT is being turned off.
-                if (mAdapter == null) break;
+                if (mAdapter == null) {
+                    break;
+                }
                 int state = mAdapter.getState();
-                if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
-                    (state != BluetoothAdapter.STATE_ON)) {
+                if ((state != BluetoothAdapter.STATE_TURNING_ON) && (state
+                        != BluetoothAdapter.STATE_ON)) {
                     Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
                     break;
                 }
                 try {
-                    if (VERBOSE) Log.v(TAG, "wait 300 ms");
+                    if (VERBOSE) {
+                        Log.v(TAG, "wait 300 ms");
+                    }
                     Thread.sleep(300);
                 } catch (InterruptedException e) {
                     Log.e(TAG, "socketAcceptThread thread was interrupted (3)", e);
@@ -177,7 +191,9 @@
         }
 
         if (initSocketOK) {
-            if (VERBOSE) Log.v(TAG, "Succeed to create listening socket ");
+            if (VERBOSE) {
+                Log.v(TAG, "Succeed to create listening socket ");
+            }
 
         } else {
             Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
@@ -185,7 +201,7 @@
         return initSocketOK;
     }
 
-    private final synchronized void closeServerSocket() {
+    private synchronized void closeServerSocket() {
         // exit SocketAcceptThread early
         if (mServerSocket != null) {
             try {
@@ -197,7 +213,8 @@
             }
         }
     }
-    private final synchronized void closeConnectionSocket() {
+
+    private synchronized void closeConnectionSocket() {
         if (mConnSocket != null) {
             try {
                 mConnSocket.close();
@@ -208,8 +225,10 @@
         }
     }
 
-    private final void closeService() {
-        if (VERBOSE) Log.v(TAG, "SAP Service closeService in");
+    private void closeService() {
+        if (VERBOSE) {
+            Log.v(TAG, "SAP Service closeService in");
+        }
 
         // exit initSocket early
         mInterrupted = true;
@@ -234,31 +253,36 @@
 
         closeConnectionSocket();
 
-        if (VERBOSE) Log.v(TAG, "SAP Service closeService out");
+        if (VERBOSE) {
+            Log.v(TAG, "SAP Service closeService out");
+        }
     }
 
-    private final void startSapServerSession() throws IOException {
-        if (VERBOSE) Log.v(TAG, "Sap Service startSapServerSession");
+    private void startSapServerSession() throws IOException {
+        if (VERBOSE) {
+            Log.v(TAG, "Sap Service startSapServerSession");
+        }
 
         // acquire the wakeLock before start SAP transaction thread
         if (mWakeLock == null) {
-            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
-            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                    "StartingSapTransaction");
+            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StartingSapTransaction");
             mWakeLock.setReferenceCounted(false);
             mWakeLock.acquire();
         }
 
         /* Start the SAP I/O thread and associate with message handler */
-        mSapServer = new SapServer(mSessionStatusHandler, this, mConnSocket.getInputStream(), mConnSocket.getOutputStream());
+        mSapServer = new SapServer(mSessionStatusHandler, this, mConnSocket.getInputStream(),
+                mConnSocket.getOutputStream());
         mSapServer.start();
         /* Warning: at this point we most likely have already handled the initial connect
          *          request from the SAP client, hence we need to be prepared to handle the
          *          response. (the SapHandler should have been started before this point)*/
 
         mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
-        mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
-                .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
+        mSessionStatusHandler.sendMessageDelayed(
+                mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
+                RELEASE_WAKE_LOCK_DELAY);
 
         if (VERBOSE) {
             Log.v(TAG, "startSapServerSession() success!");
@@ -269,7 +293,9 @@
 
         /* When we reach this point, the SapServer is closed down, and the client is
          * supposed to close the RFCOMM connection. */
-        if (VERBOSE) Log.v(TAG, "SAP Service stopSapServerSession");
+        if (VERBOSE) {
+            Log.v(TAG, "SAP Service stopSapServerSession");
+        }
 
         mAcceptThread = null;
         closeConnectionSocket();
@@ -297,7 +323,7 @@
      */
     private class SocketAcceptThread extends Thread {
 
-        private boolean stopped = false;
+        private boolean mStopped = false;
 
         @Override
         public void run() {
@@ -308,16 +334,20 @@
                 }
             }
 
-            while (!stopped) {
+            while (!mStopped) {
                 try {
-                    if (VERBOSE) Log.v(TAG, "Accepting socket connection...");
+                    if (VERBOSE) {
+                        Log.v(TAG, "Accepting socket connection...");
+                    }
                     serverSocket = mServerSocket;
                     if (serverSocket == null) {
                         Log.w(TAG, "mServerSocket is null");
                         break;
                     }
                     mConnSocket = mServerSocket.accept();
-                    if (VERBOSE) Log.v(TAG, "Accepted socket connection...");
+                    if (VERBOSE) {
+                        Log.v(TAG, "Accepted socket connection...");
+                    }
                     synchronized (SapService.this) {
                         if (mConnSocket == null) {
                             Log.w(TAG, "mConnSocket is null");
@@ -337,22 +367,26 @@
                     }
                     int permission = mRemoteDevice.getSimAccessPermission();
 
-                    if (VERBOSE) Log.v(TAG, "getSimAccessPermission() = " + permission);
+                    if (VERBOSE) {
+                        Log.v(TAG, "getSimAccessPermission() = " + permission);
+                    }
 
                     if (permission == BluetoothDevice.ACCESS_ALLOWED) {
                         try {
-                            if (VERBOSE) Log.v(TAG, "incoming connection accepted from: "
-                                + sRemoteDeviceName + " automatically as trusted device");
+                            if (VERBOSE) {
+                                Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName
+                                        + " automatically as trusted device");
+                            }
                             startSapServerSession();
                         } catch (IOException ex) {
                             Log.e(TAG, "catch exception starting obex server session", ex);
                         }
-                    } else if (permission != BluetoothDevice.ACCESS_REJECTED){
-                        Intent intent = new
-                                Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
+                    } else if (permission != BluetoothDevice.ACCESS_REJECTED) {
+                        Intent intent =
+                                new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
                         intent.setPackage(getString(R.string.pairing_ui_package));
                         intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-                                        BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
+                                BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
                         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
                         intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
 
@@ -360,27 +394,32 @@
                         setUserTimeoutAlarm();
                         sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
 
-                        if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
-                                + sRemoteDeviceName);
+                        if (VERBOSE) {
+                            Log.v(TAG, "waiting for authorization for connection from: "
+                                    + sRemoteDeviceName);
+                        }
 
                     } else {
                         // Close RFCOMM socket for current connection and start listening
                         // again for new connections.
-                        Log.w(TAG, "Can't connect with " + sRemoteDeviceName +
-                            " as access is rejected");
-                        if (mSessionStatusHandler != null)
+                        Log.w(TAG, "Can't connect with " + sRemoteDeviceName
+                                + " as access is rejected");
+                        if (mSessionStatusHandler != null) {
                             mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
+                        }
                     }
-                    stopped = true; // job done ,close this thread;
+                    mStopped = true; // job done ,close this thread;
                 } catch (IOException ex) {
-                    stopped=true;
-                    if (VERBOSE) Log.v(TAG, "Accept exception: ", ex);
+                    mStopped = true;
+                    if (VERBOSE) {
+                        Log.v(TAG, "Accept exception: ", ex);
+                    }
                 }
             }
         }
 
         void shutdown() {
-            stopped = true;
+            mStopped = true;
             interrupt();
         }
     }
@@ -388,7 +427,9 @@
     private final Handler mSessionStatusHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
-            if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
+            if (VERBOSE) {
+                Log.v(TAG, "Handler(): got msg=" + msg.what);
+            }
 
             switch (msg.what) {
                 case START_LISTENER:
@@ -413,31 +454,41 @@
                     // handled elsewhere
                     break;
                 case MSG_ACQUIRE_WAKE_LOCK:
-                    if (VERBOSE)Log.i(TAG, "Acquire Wake Lock request message");
+                    if (VERBOSE) {
+                        Log.i(TAG, "Acquire Wake Lock request message");
+                    }
                     if (mWakeLock == null) {
-                        PowerManager pm = (PowerManager)getSystemService(
-                                          Context.POWER_SERVICE);
+                        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
                         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                                    "StartingObexMapTransaction");
+                                "StartingObexMapTransaction");
                         mWakeLock.setReferenceCounted(false);
                     }
                     if (!mWakeLock.isHeld()) {
                         mWakeLock.acquire();
-                        if (DEBUG)Log.i(TAG, "  Acquired Wake Lock by message");
+                        if (DEBUG) {
+                            Log.i(TAG, "  Acquired Wake Lock by message");
+                        }
                     }
                     mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
-                    mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
-                      .obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
+                    mSessionStatusHandler.sendMessageDelayed(
+                            mSessionStatusHandler.obtainMessage(MSG_RELEASE_WAKE_LOCK),
+                            RELEASE_WAKE_LOCK_DELAY);
                     break;
                 case MSG_RELEASE_WAKE_LOCK:
-                    if (VERBOSE)Log.i(TAG, "Release Wake Lock request message");
+                    if (VERBOSE) {
+                        Log.i(TAG, "Release Wake Lock request message");
+                    }
                     if (mWakeLock != null) {
                         mWakeLock.release();
-                        if (DEBUG) Log.i(TAG, "  Released Wake Lock by message");
+                        if (DEBUG) {
+                            Log.i(TAG, "  Released Wake Lock by message");
+                        }
                     }
                     break;
                 case MSG_CHANGE_STATE:
-                    if (DEBUG) Log.d(TAG, "change state message: newState = " + msg.arg1);
+                    if (DEBUG) {
+                        Log.d(TAG, "change state message: newState = " + msg.arg1);
+                    }
                     setState(msg.arg1);
                     break;
                 case SHUTDOWN:
@@ -457,8 +508,12 @@
 
     private synchronized void setState(int state, int result) {
         if (state != mState) {
-            if (DEBUG) Log.d(TAG, "Sap state " + mState + " -> " + state + ", result = "
-                    + result);
+            if (DEBUG) {
+                Log.d(TAG, "Sap state " + mState + " -> " + state + ", result = " + result);
+            }
+            if (state == BluetoothProfile.STATE_CONNECTED) {
+                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.SAP);
+            }
             int prevState = mState;
             mState = state;
             Intent intent = new Intent(BluetoothSap.ACTION_CONNECTION_STATE_CHANGED);
@@ -501,7 +556,7 @@
 
     public List<BluetoothDevice> getConnectedDevices() {
         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-        synchronized(this) {
+        synchronized (this) {
             if (mState == BluetoothSap.STATE_CONNECTED && mRemoteDevice != null) {
                 devices.add(mRemoteDevice);
             }
@@ -520,7 +575,7 @@
                     continue;
                 }
                 connectionState = getConnectionState(device);
-                for(int i = 0; i < states.length; i++) {
+                for (int i = 0; i < states.length; i++) {
                     if (connectionState == states[i]) {
                         deviceList.add(device);
                     }
@@ -531,7 +586,7 @@
     }
 
     public int getConnectionState(BluetoothDevice device) {
-        synchronized(this) {
+        synchronized (this) {
             if (getState() == BluetoothSap.STATE_CONNECTED && getRemoteDevice().equals(device)) {
                 return BluetoothProfile.STATE_CONNECTED;
             } else {
@@ -542,16 +597,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);
+                Settings.Global.getBluetoothSapPriorityKey(device.getAddress()), priority);
+        if (DEBUG) {
+            Log.d(TAG, "Saved priority " + device + " = " + priority);
+        }
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
         int priority = Settings.Global.getInt(getContentResolver(),
-            Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
-            BluetoothProfile.PRIORITY_UNDEFINED);
+                Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
+                BluetoothProfile.PRIORITY_UNDEFINED);
         return priority;
     }
 
@@ -573,55 +629,85 @@
             registerReceiver(mSapReceiver, filter);
             mIsRegistered = true;
         } catch (Exception e) {
-            Log.w(TAG,"Unable to register sap receiver",e);
+            Log.w(TAG, "Unable to register sap receiver", e);
         }
         mInterrupted = false;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         // start RFCOMM listener
-        mSessionStatusHandler.sendMessage(mSessionStatusHandler
-                .obtainMessage(START_LISTENER));
+        mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
+        setSapService(this);
         return true;
     }
 
     @Override
     protected boolean stop() {
         Log.v(TAG, "stop()");
-        if (!mIsRegistered){
+        if (!mIsRegistered) {
             Log.i(TAG, "Avoid unregister when receiver it is not registered");
             return true;
         }
+        setSapService(null);
         try {
             mIsRegistered = false;
             unregisterReceiver(mSapReceiver);
         } catch (Exception e) {
-            Log.w(TAG,"Unable to unregister sap receiver",e);
+            Log.w(TAG, "Unable to unregister sap receiver", e);
         }
         setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
         sendShutdownMessage();
         return true;
     }
 
-    public boolean cleanup()  {
+    @Override
+    public void cleanup() {
         setState(BluetoothSap.STATE_DISCONNECTED, BluetoothSap.RESULT_CANCELED);
         closeService();
         if (mSessionStatusHandler != null) {
             mSessionStatusHandler.removeCallbacksAndMessages(null);
         }
-        return true;
     }
 
-    private void setUserTimeoutAlarm(){
-        if (DEBUG) Log.d(TAG, "setUserTimeOutAlarm()");
+    /**
+     * Get the current instance of {@link SapService}
+     *
+     * @return current instance of {@link SapService}
+     */
+    @VisibleForTesting
+    public static synchronized SapService getSapService() {
+        if (sSapService == null) {
+            Log.w(TAG, "getSapService(): service is null");
+            return null;
+        }
+        if (!sSapService.isAvailable()) {
+            Log.w(TAG, "getSapService(): service is not available");
+            return null;
+        }
+        return sSapService;
+    }
+
+    private static synchronized void setSapService(SapService instance) {
+        if (DEBUG) {
+            Log.d(TAG, "setSapService(): set to: " + instance);
+        }
+        sSapService = instance;
+    }
+
+    private void setUserTimeoutAlarm() {
+        if (DEBUG) {
+            Log.d(TAG, "setUserTimeOutAlarm()");
+        }
         cancelUserTimeoutAlarm();
         mRemoveTimeoutMsg = true;
         Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
         PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
-        mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
-                + USER_CONFIRM_TIMEOUT_VALUE,pIntent);
+        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                System.currentTimeMillis() + USER_CONFIRM_TIMEOUT_VALUE, pIntent);
     }
 
     private void cancelUserTimeoutAlarm() {
-        if (DEBUG) Log.d(TAG, "cancelUserTimeOutAlarm()");
+        if (DEBUG) {
+            Log.d(TAG, "cancelUserTimeOutAlarm()");
+        }
         if (mAlarmManager == null) {
             mAlarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
         }
@@ -638,7 +724,7 @@
         intent.setPackage(getString(R.string.pairing_ui_package));
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-                        BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
+                BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
         sendBroadcast(intent, BLUETOOTH_PERM);
     }
 
@@ -646,8 +732,7 @@
         /* Any pending messages are no longer valid.
         To speed up things, simply delete them. */
         if (mRemoveTimeoutMsg) {
-            Intent timeoutIntent =
-                    new Intent(USER_CONFIRM_TIMEOUT_ACTION);
+            Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
             sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
             mIsWaitingAuthorization = false;
             cancelUserTimeoutAlarm();
@@ -659,7 +744,9 @@
     }
 
     private void sendConnectTimeoutMessage() {
-        if (DEBUG) Log.d(TAG, "sendConnectTimeoutMessage()");
+        if (DEBUG) {
+            Log.d(TAG, "sendConnectTimeoutMessage()");
+        }
         if (mSessionStatusHandler != null) {
             Message msg = mSessionStatusHandler.obtainMessage(USER_TIMEOUT);
             msg.sendToTarget();
@@ -672,19 +759,25 @@
         @Override
         public void onReceive(Context context, Intent intent) {
 
-            if (VERBOSE) Log.v(TAG, "onReceive");
+            if (VERBOSE) {
+                Log.v(TAG, "onReceive");
+            }
             String action = intent.getAction();
             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
-                                               BluetoothAdapter.ERROR);
+                int state =
+                        intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
                 if (state == BluetoothAdapter.STATE_TURNING_OFF) {
-                    if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF");
+                    if (DEBUG) {
+                        Log.d(TAG, "STATE_TURNING_OFF");
+                    }
                     sendShutdownMessage();
                 } else if (state == BluetoothAdapter.STATE_ON) {
-                    if (DEBUG) Log.d(TAG, "STATE_ON");
+                    if (DEBUG) {
+                        Log.d(TAG, "STATE_ON");
+                    }
                     // start RFCOMM listener
-                    mSessionStatusHandler.sendMessage(mSessionStatusHandler
-                                  .obtainMessage(START_LISTENER));
+                    mSessionStatusHandler.sendMessage(
+                            mSessionStatusHandler.obtainMessage(START_LISTENER));
                 }
                 return;
             }
@@ -699,8 +792,8 @@
                 mIsWaitingAuthorization = false;
 
                 if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
-                                       BluetoothDevice.CONNECTION_ACCESS_NO) ==
-                    BluetoothDevice.CONNECTION_ACCESS_YES) {
+                        BluetoothDevice.CONNECTION_ACCESS_NO)
+                        == BluetoothDevice.CONNECTION_ACCESS_YES) {
                     //bluetooth connection accepted by user
                     if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
                         boolean result = mRemoteDevice.setSimAccessPermission(
@@ -724,8 +817,7 @@
                         boolean result = mRemoteDevice.setSimAccessPermission(
                                 BluetoothDevice.ACCESS_REJECTED);
                         if (VERBOSE) {
-                            Log.v(TAG, "setSimAccessPermission(ACCESS_REJECTED) result="
-                                    + result);
+                            Log.v(TAG, "setSimAccessPermission(ACCESS_REJECTED) result=" + result);
                         }
                     }
                     // Ensure proper cleanup, and prepare for new connect.
@@ -735,7 +827,9 @@
             }
 
             if (action.equals(USER_CONFIRM_TIMEOUT_ACTION)) {
-                if (DEBUG) Log.d(TAG, "USER_CONFIRM_TIMEOUT_ACTION Received.");
+                if (DEBUG) {
+                    Log.d(TAG, "USER_CONFIRM_TIMEOUT_ACTION Received.");
+                }
                 // send us self a message about the timeout.
                 sendConnectTimeoutMessage();
                 return;
@@ -749,7 +843,9 @@
                     return;
                 }
 
-                if (DEBUG) Log.d(TAG,"ACL disconnected for " + device);
+                if (DEBUG) {
+                    Log.d(TAG, "ACL disconnected for " + device);
+                }
 
                 if (mRemoteDevice.equals(device)) {
                     if (mRemoveTimeoutMsg) {
@@ -763,25 +859,28 @@
                 }
             }
         }
-    };
+    }
+
+    ;
 
     //Binder object: Must be static class or memory leak may occur
+
     /**
      * This class implements the IBluetoothSap interface - or actually it validates the
      * preconditions for calling the actual functionality in the SapService, and calls it.
      */
-    private static class SapBinder extends IBluetoothSap.Stub
-        implements IProfileServiceBinder {
+    private static class SapBinder extends IBluetoothSap.Stub implements IProfileServiceBinder {
         private SapService mService;
 
         private SapService getService() {
             if (!Utils.checkCaller()) {
-                Log.w(TAG,"call not allowed for non-active user");
+                Log.w(TAG, "call not allowed for non-active user");
                 return null;
             }
 
-            if (mService != null && mService.isAvailable() ) {
-                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            if (mService != null && mService.isAvailable()) {
+                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                        "Need BLUETOOTH permission");
                 return mService;
             }
             return null;
@@ -792,78 +891,108 @@
             mService = service;
         }
 
-        public boolean cleanup()  {
+        @Override
+        public void cleanup() {
             mService = null;
-            return true;
         }
 
+        @Override
         public int getState() {
             Log.v(TAG, "getState()");
             SapService service = getService();
-            if (service == null) return BluetoothSap.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothSap.STATE_DISCONNECTED;
+            }
             return getService().getState();
         }
 
+        @Override
         public BluetoothDevice getClient() {
             Log.v(TAG, "getClient()");
             SapService service = getService();
-            if (service == null) return null;
+            if (service == null) {
+                return null;
+            }
             Log.v(TAG, "getClient() - returning " + service.getRemoteDevice());
             return service.getRemoteDevice();
         }
 
+        @Override
         public boolean isConnected(BluetoothDevice device) {
             Log.v(TAG, "isConnected()");
             SapService service = getService();
-            if (service == null) return false;
-            return (service.getState() == BluetoothSap.STATE_CONNECTED
-                    && service.getRemoteDevice().equals(device));
+            if (service == null) {
+                return false;
+            }
+            return (service.getState() == BluetoothSap.STATE_CONNECTED && service.getRemoteDevice()
+                    .equals(device));
         }
 
+        @Override
         public boolean connect(BluetoothDevice device) {
             Log.v(TAG, "connect()");
             SapService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return false;
         }
 
+        @Override
         public boolean disconnect(BluetoothDevice device) {
             Log.v(TAG, "disconnect()");
             SapService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.disconnect(device);
         }
 
+        @Override
         public List<BluetoothDevice> getConnectedDevices() {
             Log.v(TAG, "getConnectedDevices()");
             SapService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getConnectedDevices();
         }
 
+        @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
             Log.v(TAG, "getDevicesMatchingConnectionStates()");
             SapService service = getService();
-            if (service == null) return new ArrayList<BluetoothDevice>(0);
+            if (service == null) {
+                return new ArrayList<BluetoothDevice>(0);
+            }
             return service.getDevicesMatchingConnectionStates(states);
         }
 
+        @Override
         public int getConnectionState(BluetoothDevice device) {
             Log.v(TAG, "getConnectionState()");
             SapService service = getService();
-            if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
             return service.getConnectionState(device);
         }
 
+        @Override
         public boolean setPriority(BluetoothDevice device, int priority) {
             SapService service = getService();
-            if (service == null) return false;
+            if (service == null) {
+                return false;
+            }
             return service.setPriority(device, priority);
         }
 
+        @Override
         public int getPriority(BluetoothDevice device) {
             SapService service = getService();
-            if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
+            if (service == null) {
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+            }
             return service.getPriority(device);
         }
     }
diff --git a/src/com/android/bluetooth/sdp/SdpManager.java b/src/com/android/bluetooth/sdp/SdpManager.java
index 6e4049e..d0cca25 100644
--- a/src/com/android/bluetooth/sdp/SdpManager.java
+++ b/src/com/android/bluetooth/sdp/SdpManager.java
@@ -19,8 +19,8 @@
 import android.bluetooth.SdpMnsRecord;
 import android.bluetooth.SdpOppOpsRecord;
 import android.bluetooth.SdpPseRecord;
-import android.bluetooth.SdpSapsRecord;
 import android.bluetooth.SdpRecord;
+import android.bluetooth.SdpSapsRecord;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Message;
@@ -39,22 +39,22 @@
 
     private static final boolean D = true;
     private static final boolean V = false;
-    private static final String TAG="SdpManager";
+    private static final String TAG = "SdpManager";
 
     // TODO: When changing PBAP to use this new API.
     //       Move the defines to the profile (PBAP already have the feature bits)
     /* PBAP repositories */
-    public static final byte PBAP_REPO_LOCAL        = 0x01<<0;
-    public static final byte PBAP_REPO_SIM          = 0x01<<1;
-    public static final byte PBAP_REPO_SPEED_DAIL   = 0x01<<2;
-    public static final byte PBAP_REPO_FAVORITES    = 0x01<<3;
+    public static final byte PBAP_REPO_LOCAL = 0x01 << 0;
+    public static final byte PBAP_REPO_SIM = 0x01 << 1;
+    public static final byte PBAP_REPO_SPEED_DAIL = 0x01 << 2;
+    public static final byte PBAP_REPO_FAVORITES = 0x01 << 3;
 
     /* Variables to keep track of ongoing and queued search requests.
      * mTrackerLock must be held, when using/changing sSdpSearchTracker
      * and mSearchInProgress. */
     static SdpSearchTracker sSdpSearchTracker;
-    static boolean mSearchInProgress = false;
-    static Object mTrackerLock = new Object();
+    static boolean sSearchInProgress = false;
+    static final Object TRACKER_LOCK = new Object();
 
     /* The timeout to wait for reply from native. Should never fire. */
     private static final int SDP_INTENT_DELAY = 11000;
@@ -71,27 +71,30 @@
         classInitNative();
     }
 
-    private native static void classInitNative();
+    private static native void classInitNative();
+
     private native void initializeNative();
+
     private native void cleanupNative();
+
     private native boolean sdpSearchNative(byte[] address, byte[] uuid);
 
-    private native int sdpCreateMapMasRecordNative(String serviceName, int masId,
-            int rfcommChannel, int l2capPsm, int version, int msgTypes, int features);
+    private native int sdpCreateMapMasRecordNative(String serviceName, int masId, int rfcommChannel,
+            int l2capPsm, int version, int msgTypes, int features);
 
-    private native int sdpCreateMapMnsRecordNative(String serviceName,
-            int rfcommChannel, int l2capPsm, int version, int features);
+    private native int sdpCreateMapMnsRecordNative(String serviceName, int rfcommChannel,
+            int l2capPsm, int version, int features);
 
     private native int sdpCreatePbapPseRecordNative(String serviceName, int rfcommChannel,
             int l2capPsm, int version, int repositories, int features);
 
-    private native int sdpCreateOppOpsRecordNative(String serviceName,
-            int rfcommChannel, int l2capPsm, int version, byte[] formats_list);
+    private native int sdpCreateOppOpsRecordNative(String serviceName, int rfcommChannel,
+            int l2capPsm, int version, byte[] formatsList);
 
     private native int sdpCreateSapsRecordNative(String serviceName, int rfcommChannel,
             int version);
 
-    private native boolean sdpRemoveSdpRecordNative(int record_id);
+    private native boolean sdpRemoveSdpRecordNative(int recordId);
 
 
     /* Inner class used for wrapping sdp search instance data */
@@ -100,22 +103,26 @@
         private final ParcelUuid mUuid;
         private int mStatus = 0;
         private boolean mSearching;
+
         /* TODO: If we change the API to use another mechanism than intents for
          *       delivering the results, this would be the place to keep a list
          *       of the objects to deliver the results to. */
-        public SdpSearchInstance(int status, BluetoothDevice device, ParcelUuid uuid){
+        SdpSearchInstance(int status, BluetoothDevice device, ParcelUuid uuid) {
             this.mDevice = device;
             this.mUuid = uuid;
             this.mStatus = status;
             mSearching = true;
         }
+
         public BluetoothDevice getDevice() {
             return mDevice;
         }
+
         public ParcelUuid getUuid() {
             return mUuid;
         }
-        public int getStatus(){
+
+        public int getStatus() {
             return mStatus;
         }
 
@@ -130,11 +137,12 @@
         }
 
         public void stopSearch() {
-            if(mSearching) {
+            if (mSearching) {
                 mHandler.removeMessages(MESSAGE_SDP_INTENT, this);
             }
             mSearching = false;
         }
+
         public boolean isSearching() {
             return mSearching;
         }
@@ -146,23 +154,23 @@
      * As we use a mix of byte[] and object instances, this is more
      * efficient than implementing comparable. */
     class SdpSearchTracker {
-        private final ArrayList<SdpSearchInstance> list = new ArrayList<SdpSearchInstance>();
+        private final ArrayList<SdpSearchInstance> mList = new ArrayList<SdpSearchInstance>();
 
         void clear() {
-            list.clear();
+            mList.clear();
         }
 
-        boolean add(SdpSearchInstance inst){
-            return list.add(inst);
+        boolean add(SdpSearchInstance inst) {
+            return mList.add(inst);
         }
 
         boolean remove(SdpSearchInstance inst) {
-            return list.remove(inst);
+            return mList.remove(inst);
         }
 
         SdpSearchInstance getNext() {
-            if(list.size() > 0) {
-                return list.get(0);
+            if (mList.size() > 0) {
+                return mList.get(0);
             }
             return null;
         }
@@ -170,9 +178,9 @@
         SdpSearchInstance getSearchInstance(byte[] address, byte[] uuidBytes) {
             String addressString = Utils.getAddressStringFromByte(address);
             ParcelUuid uuid = Utils.byteArrayToUuid(uuidBytes)[0];
-            for (SdpSearchInstance inst : list) {
-                if (inst.getDevice().getAddress().equals(addressString)
-                        && inst.getUuid().equals(uuid)) {
+            for (SdpSearchInstance inst : mList) {
+                if (inst.getDevice().getAddress().equals(addressString) && inst.getUuid()
+                        .equals(uuid)) {
                     return inst;
                 }
             }
@@ -181,9 +189,9 @@
 
         boolean isSearching(BluetoothDevice device, ParcelUuid uuid) {
             String addressString = device.getAddress();
-            for (SdpSearchInstance inst : list) {
-                if (inst.getDevice().getAddress().equals(addressString)
-                        && inst.getUuid().equals(uuid)) {
+            for (SdpSearchInstance inst : mList) {
+                if (inst.getDevice().getAddress().equals(addressString) && inst.getUuid()
+                        .equals(uuid)) {
                     return inst.isSearching();
                 }
             }
@@ -198,7 +206,7 @@
         /* This is only needed until intents are no longer used */
         sAdapterService = adapterService;
         initializeNative();
-        sNativeAvailable=true;
+        sNativeAvailable = true;
     }
 
 
@@ -212,31 +220,25 @@
     }
 
     public void cleanup() {
-        if (sSdpSearchTracker !=null) {
-            synchronized(mTrackerLock) {
+        if (sSdpSearchTracker != null) {
+            synchronized (TRACKER_LOCK) {
                 sSdpSearchTracker.clear();
             }
         }
 
         if (sNativeAvailable) {
             cleanupNative();
-            sNativeAvailable=false;
+            sNativeAvailable = false;
         }
         sSdpManager = null;
     }
 
 
-    void sdpMasRecordFoundCallback(int status, byte[] address, byte[] uuid,
-            int masInstanceId,
-            int l2capPsm,
-            int rfcommCannelNumber,
-            int profileVersion,
-            int supportedFeatures,
-            int supportedMessageTypes,
-            String serviceName,
-            boolean moreResults) {
+    void sdpMasRecordFoundCallback(int status, byte[] address, byte[] uuid, int masInstanceId,
+            int l2capPsm, int rfcommCannelNumber, int profileVersion, int supportedFeatures,
+            int supportedMessageTypes, String serviceName, boolean moreResults) {
 
-        synchronized(mTrackerLock) {
+        synchronized (TRACKER_LOCK) {
             SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid);
             SdpMasRecord sdpRecord = null;
             if (inst == null) {
@@ -244,29 +246,24 @@
                 return;
             }
             inst.setStatus(status);
-            if(status == AbstractionLayer.BT_STATUS_SUCCESS) {
-                sdpRecord = new SdpMasRecord(masInstanceId,
-                                             l2capPsm,
-                                             rfcommCannelNumber,
-                                             profileVersion,
-                                             supportedFeatures,
-                                             supportedMessageTypes,
-                                             serviceName);
+            if (status == AbstractionLayer.BT_STATUS_SUCCESS) {
+                sdpRecord = new SdpMasRecord(masInstanceId, l2capPsm, rfcommCannelNumber,
+                        profileVersion, supportedFeatures, supportedMessageTypes, serviceName);
             }
-            if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid));
-            if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            if (D) {
+                Log.d(TAG, "UUID: " + Arrays.toString(uuid));
+            }
+            if (D) {
+                Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            }
             sendSdpIntent(inst, sdpRecord, moreResults);
         }
     }
 
-    void sdpMnsRecordFoundCallback(int status, byte[] address, byte[] uuid,
-            int l2capPsm,
-            int rfcommCannelNumber,
-            int profileVersion,
-            int supportedFeatures,
-            String serviceName,
+    void sdpMnsRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm,
+            int rfcommCannelNumber, int profileVersion, int supportedFeatures, String serviceName,
             boolean moreResults) {
-        synchronized(mTrackerLock) {
+        synchronized (TRACKER_LOCK) {
 
             SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid);
             SdpMnsRecord sdpRecord = null;
@@ -275,28 +272,24 @@
                 return;
             }
             inst.setStatus(status);
-            if(status == AbstractionLayer.BT_STATUS_SUCCESS) {
-                sdpRecord = new SdpMnsRecord(l2capPsm,
-                                             rfcommCannelNumber,
-                                             profileVersion,
-                                             supportedFeatures,
-                                             serviceName);
+            if (status == AbstractionLayer.BT_STATUS_SUCCESS) {
+                sdpRecord = new SdpMnsRecord(l2capPsm, rfcommCannelNumber, profileVersion,
+                        supportedFeatures, serviceName);
             }
-            if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid));
-            if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            if (D) {
+                Log.d(TAG, "UUID: " + Arrays.toString(uuid));
+            }
+            if (D) {
+                Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            }
             sendSdpIntent(inst, sdpRecord, moreResults);
         }
     }
 
-    void sdpPseRecordFoundCallback(int status, byte[] address, byte[] uuid,
-                                        int l2capPsm,
-                                        int rfcommCannelNumber,
-                                        int profileVersion,
-                                        int supportedFeatures,
-                                        int supportedRepositories,
-                                        String serviceName,
-                                        boolean moreResults) {
-        synchronized(mTrackerLock) {
+    void sdpPseRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm,
+            int rfcommCannelNumber, int profileVersion, int supportedFeatures,
+            int supportedRepositories, String serviceName, boolean moreResults) {
+        synchronized (TRACKER_LOCK) {
             SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid);
             SdpPseRecord sdpRecord = null;
             if (inst == null) {
@@ -304,29 +297,25 @@
                 return;
             }
             inst.setStatus(status);
-            if(status == AbstractionLayer.BT_STATUS_SUCCESS) {
-                sdpRecord = new SdpPseRecord(l2capPsm,
-                                             rfcommCannelNumber,
-                                             profileVersion,
-                                             supportedFeatures,
-                                             supportedRepositories,
-                                             serviceName);
+            if (status == AbstractionLayer.BT_STATUS_SUCCESS) {
+                sdpRecord = new SdpPseRecord(l2capPsm, rfcommCannelNumber, profileVersion,
+                        supportedFeatures, supportedRepositories, serviceName);
             }
-            if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid));
-            if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            if (D) {
+                Log.d(TAG, "UUID: " + Arrays.toString(uuid));
+            }
+            if (D) {
+                Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            }
             sendSdpIntent(inst, sdpRecord, moreResults);
         }
     }
 
-    void sdpOppOpsRecordFoundCallback(int status, byte[] address, byte[] uuid,
-            int l2capPsm,
-            int rfcommCannelNumber,
-            int profileVersion,
-            String serviceName,
-            byte[] formatsList,
+    void sdpOppOpsRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm,
+            int rfcommCannelNumber, int profileVersion, String serviceName, byte[] formatsList,
             boolean moreResults) {
 
-        synchronized(mTrackerLock) {
+        synchronized (TRACKER_LOCK) {
             SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid);
             SdpOppOpsRecord sdpRecord = null;
 
@@ -335,26 +324,24 @@
                 return;
             }
             inst.setStatus(status);
-            if(status == AbstractionLayer.BT_STATUS_SUCCESS) {
-                sdpRecord = new SdpOppOpsRecord(serviceName,
-                                                rfcommCannelNumber,
-                                                l2capPsm,
-                                                profileVersion,
-                                                formatsList);
+            if (status == AbstractionLayer.BT_STATUS_SUCCESS) {
+                sdpRecord = new SdpOppOpsRecord(serviceName, rfcommCannelNumber, l2capPsm,
+                        profileVersion, formatsList);
             }
-            if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid));
-            if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            if (D) {
+                Log.d(TAG, "UUID: " + Arrays.toString(uuid));
+            }
+            if (D) {
+                Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            }
             sendSdpIntent(inst, sdpRecord, moreResults);
         }
     }
 
-    void sdpSapsRecordFoundCallback(int status, byte[] address, byte[] uuid,
-            int rfcommCannelNumber,
-            int profileVersion,
-            String serviceName,
-            boolean moreResults) {
+    void sdpSapsRecordFoundCallback(int status, byte[] address, byte[] uuid, int rfcommCannelNumber,
+            int profileVersion, String serviceName, boolean moreResults) {
 
-        synchronized(mTrackerLock) {
+        synchronized (TRACKER_LOCK) {
             SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid);
             SdpSapsRecord sdpRecord = null;
             if (inst == null) {
@@ -363,20 +350,22 @@
             }
             inst.setStatus(status);
             if (status == AbstractionLayer.BT_STATUS_SUCCESS) {
-                sdpRecord = new SdpSapsRecord(rfcommCannelNumber,
-                                             profileVersion,
-                                             serviceName);
+                sdpRecord = new SdpSapsRecord(rfcommCannelNumber, profileVersion, serviceName);
             }
-            if (D) Log.d(TAG, "UUID: " + Arrays.toString(uuid));
-            if (D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            if (D) {
+                Log.d(TAG, "UUID: " + Arrays.toString(uuid));
+            }
+            if (D) {
+                Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            }
             sendSdpIntent(inst, sdpRecord, moreResults);
         }
     }
 
     /* TODO: Test or remove! */
-    void sdpRecordFoundCallback(int status, byte[] address, byte[] uuid,
-            int size_record, byte[] record) {
-        synchronized(mTrackerLock) {
+    void sdpRecordFoundCallback(int status, byte[] address, byte[] uuid, int sizeRecord,
+            byte[] record) {
+        synchronized (TRACKER_LOCK) {
 
             SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid);
             SdpRecord sdpRecord = null;
@@ -385,24 +374,31 @@
                 return;
             }
             inst.setStatus(status);
-            if(status == AbstractionLayer.BT_STATUS_SUCCESS) {
-                if(D) Log.d(TAG, "sdpRecordFoundCallback: found a sdp record of size "
-                        + size_record );
-                if(D) Log.d(TAG, "Record:"+ Arrays.toString(record));
-                sdpRecord = new SdpRecord(size_record, record);
+            if (status == AbstractionLayer.BT_STATUS_SUCCESS) {
+                if (D) {
+                    Log.d(TAG, "sdpRecordFoundCallback: found a sdp record of size " + sizeRecord);
+                }
+                if (D) {
+                    Log.d(TAG, "Record:" + Arrays.toString(record));
+                }
+                sdpRecord = new SdpRecord(sizeRecord, record);
             }
-            if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid));
-            if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            if (D) {
+                Log.d(TAG, "UUID: " + Arrays.toString(uuid));
+            }
+            if (D) {
+                Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            }
             sendSdpIntent(inst, sdpRecord, false);
         }
     }
 
     public void sdpSearch(BluetoothDevice device, ParcelUuid uuid) {
-        if (sNativeAvailable == false) {
+        if (!sNativeAvailable) {
             Log.e(TAG, "Native not initialized!");
             return;
         }
-        synchronized (mTrackerLock) {
+        synchronized (TRACKER_LOCK) {
             if (sSdpSearchTracker.isSearching(device, uuid)) {
                 /* Search already in progress */
                 return;
@@ -421,25 +417,26 @@
 
         SdpSearchInstance inst = sSdpSearchTracker.getNext();
 
-        if((inst != null) && (mSearchInProgress == false)) {
-            if(D) Log.d(TAG, "Starting search for UUID: "+ inst.getUuid());
-            mSearchInProgress = true;
+        if ((inst != null) && (!sSearchInProgress)) {
+            if (D) {
+                Log.d(TAG, "Starting search for UUID: " + inst.getUuid());
+            }
+            sSearchInProgress = true;
 
             inst.startSearch(); // Trigger timeout message
 
             sdpSearchNative(Utils.getBytesFromAddress(inst.getDevice().getAddress()),
-                                            Utils.uuidToByteArray(inst.getUuid()));
-        } // Else queue is empty.
-        else {
-            if(D) Log.d(TAG, "startSearch(): nextInst = " + inst +
-                    " mSearchInProgress = " + mSearchInProgress
-                    + " - search busy or queue empty.");
+                    Utils.uuidToByteArray(inst.getUuid()));
+        } else { // Else queue is empty.
+            if (D) {
+                Log.d(TAG, "startSearch(): nextInst = " + inst + " mSearchInProgress = "
+                        + sSearchInProgress + " - search busy or queue empty.");
+            }
         }
     }
 
     /* Caller must hold the mTrackerLock */
-    private void sendSdpIntent(SdpSearchInstance inst,
-            Parcelable record, boolean moreResults) {
+    private void sendSdpIntent(SdpSearchInstance inst, Parcelable record, boolean moreResults) {
 
         inst.stopSearch();
 
@@ -447,7 +444,9 @@
 
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, inst.getDevice());
         intent.putExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, inst.getStatus());
-        if (record != null)  intent.putExtra(BluetoothDevice.EXTRA_SDP_RECORD, record);
+        if (record != null) {
+            intent.putExtra(BluetoothDevice.EXTRA_SDP_RECORD, record);
+        }
         intent.putExtra(BluetoothDevice.EXTRA_UUID, inst.getUuid());
         /* TODO:  BLUETOOTH_ADMIN_PERM was private... change to callback interface.
          * Keep in mind that the MAP client needs to use this as well,
@@ -455,10 +454,10 @@
          * part of the Bluetooth APK. */
         sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
 
-        if(moreResults == false) {
+        if (!moreResults) {
             //Remove the outstanding UUID request
             sSdpSearchTracker.remove(inst);
-            mSearchInProgress = false;
+            sSearchInProgress = false;
             startSearch();
         }
     }
@@ -467,13 +466,13 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case MESSAGE_SDP_INTENT:
-                SdpSearchInstance msgObj = (SdpSearchInstance)msg.obj;
-                Log.w(TAG, "Search timedout for UUID " + msgObj.getUuid());
-                synchronized (mTrackerLock) {
-                    sendSdpIntent(msgObj, null, false);
-                }
-                break;
+                case MESSAGE_SDP_INTENT:
+                    SdpSearchInstance msgObj = (SdpSearchInstance) msg.obj;
+                    Log.w(TAG, "Search timedout for UUID " + msgObj.getUuid());
+                    synchronized (TRACKER_LOCK) {
+                        sendSdpIntent(msgObj, null, false);
+                    }
+                    break;
             }
         }
     };
@@ -501,14 +500,13 @@
      *          creation/destruction of BluetoothSockets, hence SDP record cleanup
      *          is a separate process.
      */
-    public int createMapMasRecord(String serviceName, int masId,
-            int rfcommChannel, int l2capPsm, int version,
-            int msgTypes, int features) {
-        if(sNativeAvailable == false) {
+    public int createMapMasRecord(String serviceName, int masId, int rfcommChannel, int l2capPsm,
+            int version, int msgTypes, int features) {
+        if (!sNativeAvailable) {
             throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized");
         }
-        return sdpCreateMapMasRecordNative(serviceName, masId, rfcommChannel,
-                l2capPsm, version, msgTypes, features);
+        return sdpCreateMapMasRecordNative(serviceName, masId, rfcommChannel, l2capPsm, version,
+                msgTypes, features);
     }
 
     /**
@@ -531,13 +529,12 @@
      *          creation/destruction of BluetoothSockets, hence SDP record cleanup
      *          is a separate process.
      */
-    public int createMapMnsRecord(String serviceName, int rfcommChannel,
-            int l2capPsm, int version, int features) {
-        if(sNativeAvailable == false) {
+    public int createMapMnsRecord(String serviceName, int rfcommChannel, int l2capPsm, int version,
+            int features) {
+        if (!sNativeAvailable) {
             throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized");
         }
-        return sdpCreateMapMnsRecordNative(serviceName, rfcommChannel,
-                l2capPsm, version, features);
+        return sdpCreateMapMnsRecordNative(serviceName, rfcommChannel, l2capPsm, version, features);
     }
 
     /**
@@ -562,13 +559,13 @@
      *          creation/destruction of BluetoothSockets, hence SDP record cleanup
      *          is a separate process.
      */
-    public int createPbapPseRecord(String serviceName, int rfcommChannel, int l2capPsm,
-                                   int version, int repositories, int features) {
-        if(sNativeAvailable == false) {
+    public int createPbapPseRecord(String serviceName, int rfcommChannel, int l2capPsm, int version,
+            int repositories, int features) {
+        if (!sNativeAvailable) {
             throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized");
         }
-        return sdpCreatePbapPseRecordNative(serviceName, rfcommChannel,
-                l2capPsm, version, repositories, features);
+        return sdpCreatePbapPseRecordNative(serviceName, rfcommChannel, l2capPsm, version,
+                repositories, features);
     }
 
     /**
@@ -591,13 +588,13 @@
      *          creation/destruction of BluetoothSockets, hence SDP record cleanup
      *          is a separate process.
      */
-    public int createOppOpsRecord(String serviceName, int rfcommChannel, int l2capPsm,
-                                  int version, byte[] formatsList) {
-        if(sNativeAvailable == false) {
+    public int createOppOpsRecord(String serviceName, int rfcommChannel, int l2capPsm, int version,
+            byte[] formatsList) {
+        if (!sNativeAvailable) {
             throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized");
         }
-        return sdpCreateOppOpsRecordNative(serviceName, rfcommChannel,
-                 l2capPsm, version, formatsList);
+        return sdpCreateOppOpsRecordNative(serviceName, rfcommChannel, l2capPsm, version,
+                formatsList);
     }
 
     /**
@@ -616,22 +613,22 @@
      *          is a separate process.
      */
     public int createSapsRecord(String serviceName, int rfcommChannel, int version) {
-        if (sNativeAvailable == false) {
+        if (!sNativeAvailable) {
             throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized");
         }
         return sdpCreateSapsRecordNative(serviceName, rfcommChannel, version);
     }
 
-     /**
-      * Remove a SDP record.
-      * When Bluetooth is disabled all records will be deleted, hence there
-      * is no need to call this function when bluetooth is disabled.
-      * @param recordId The Id returned by on of the createXxxXxxRecord() functions.
-      * @return TRUE if the record removal was initiated successfully. FALSE if the record
-      *         handle is not known/have already been removed.
-      */
-    public boolean removeSdpRecord(int recordId){
-        if(sNativeAvailable == false) {
+    /**
+     * Remove a SDP record.
+     * When Bluetooth is disabled all records will be deleted, hence there
+     * is no need to call this function when bluetooth is disabled.
+     * @param recordId The Id returned by on of the createXxxXxxRecord() functions.
+     * @return TRUE if the record removal was initiated successfully. FALSE if the record
+     *         handle is not known/have already been removed.
+     */
+    public boolean removeSdpRecord(int recordId) {
+        if (!sNativeAvailable) {
             throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized");
         }
         return sdpRemoveSdpRecordNative(recordId);
diff --git a/src/com/android/bluetooth/util/DevicePolicyUtils.java b/src/com/android/bluetooth/util/DevicePolicyUtils.java
index 4cafffb..5f11a29 100644
--- a/src/com/android/bluetooth/util/DevicePolicyUtils.java
+++ b/src/com/android/bluetooth/util/DevicePolicyUtils.java
@@ -28,10 +28,10 @@
 
 public final class DevicePolicyUtils {
     private static boolean isBluetoothWorkContactSharingDisabled(Context context) {
-        final DevicePolicyManager dpm = (DevicePolicyManager) context
-                .getSystemService(Context.DEVICE_POLICY_SERVICE);
-        final UserManager userManager = (UserManager) context
-                .getSystemService(Context.USER_SERVICE);
+        final DevicePolicyManager dpm =
+                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        final UserManager userManager =
+                (UserManager) context.getSystemService(Context.USER_SERVICE);
         final int myUserId = UserHandle.myUserId();
         final List<UserInfo> userInfoList = userManager.getProfiles(myUserId);
 
diff --git a/src/com/android/bluetooth/util/Interop.java b/src/com/android/bluetooth/util/Interop.java
index 4861c15..41ac512 100644
--- a/src/com/android/bluetooth/util/Interop.java
+++ b/src/com/android/bluetooth/util/Interop.java
@@ -26,62 +26,66 @@
  */
 public class Interop {
 
-  /**
-   * Simple interop entry consisting of a workarond id (see below)
-   * and a (partial or complete) Bluetooth device address string
-   * to match against.
-   */
-  private static class Entry {
-    String address;
-    int workaround_id;
+    /**
+     * Simple interop entry consisting of a workarond id (see below)
+     * and a (partial or complete) Bluetooth device address string
+     * to match against.
+     */
+    private static class Entry {
+        public String address;
+        public int workaround_id;
 
-    public Entry(int workaround_id, String address) {
-      this.workaround_id = workaround_id;
-      this.address = address;
-    }
-  }
-
-  /**
-   * The actual "database" of interop entries.
-   */
-  private static List<Entry> entries = null;
-
-  /**
-   * Workaround ID for deivces which do not accept non-ASCII
-   * characters in SMS messages.
-   */
-  public static final int INTEROP_MAP_ASCIIONLY = 1;
-
-  /**
-   * Initializes the interop datbase with the relevant workaround
-   * entries.
-   * When adding entries, please provide a description for each
-   * device as to what problem the workaround addresses.
-   */
-  private static void lazyInitInteropDatabase() {
-    if (entries != null) return;
-    entries = new ArrayList<Entry>();
-
-    /** Mercedes Benz NTG 4.5 does not handle non-ASCII characters in SMS */
-    entries.add(new Entry(INTEROP_MAP_ASCIIONLY, "00:26:e8"));
-  }
-
-  /**
-   * Checks wheter a given device identified by |address| is a match
-   * for a given workaround identified by |workaround_id|.
-   * Return true if the address matches, false otherwise.
-   */
-  public static boolean matchByAddress(int workaround_id, String address) {
-    if (address == null || address.isEmpty()) return false;
-
-    lazyInitInteropDatabase();
-    for (Entry entry : entries) {
-      if (entry.workaround_id == workaround_id &&
-          entry.address.startsWith(address.toLowerCase())) {
-        return true;
-      }
+        Entry(int workaroundId, String address) {
+            this.workaround_id = workaroundId;
+            this.address = address;
+        }
     }
 
-    return false;
-  }
+    /**
+     * The actual "database" of interop entries.
+     */
+    private static List<Entry> sEntries = null;
+
+    /**
+     * Workaround ID for deivces which do not accept non-ASCII
+     * characters in SMS messages.
+     */
+    public static final int INTEROP_MAP_ASCIIONLY = 1;
+
+    /**
+     * Initializes the interop datbase with the relevant workaround
+     * entries.
+     * When adding entries, please provide a description for each
+     * device as to what problem the workaround addresses.
+     */
+    private static void lazyInitInteropDatabase() {
+        if (sEntries != null) {
+            return;
+        }
+        sEntries = new ArrayList<Entry>();
+
+        /** Mercedes Benz NTG 4.5 does not handle non-ASCII characters in SMS */
+        sEntries.add(new Entry(INTEROP_MAP_ASCIIONLY, "00:26:e8"));
+    }
+
+    /**
+     * Checks wheter a given device identified by |address| is a match
+     * for a given workaround identified by |workaroundId|.
+     * Return true if the address matches, false otherwise.
+     */
+    public static boolean matchByAddress(int workaroundId, String address) {
+        if (address == null || address.isEmpty()) {
+            return false;
+        }
+
+        lazyInitInteropDatabase();
+        for (Entry entry : sEntries) {
+            if (entry.workaround_id == workaroundId && entry.address.startsWith(
+                    address.toLowerCase())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/tests/Android.mk b/tests/Android.mk
old mode 100755
new mode 100644
index 744e791d..fd297e3
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -1,27 +1,5 @@
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-# We only want this apk build for tests.
-LOCAL_MODULE_TAGS := tests
-LOCAL_CERTIFICATE := platform
-
-LOCAL_JAVA_LIBRARIES := \
-    javax.obex android.test.runner \
-    telephony-common \
-    libprotobuf-java-micro
-
-LOCAL_STATIC_JAVA_LIBRARIES :=  \
-    com.android.emailcommon \
-    android-support-test \
-    mockito-target \
-    legacy-android-test
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-# LOCAL_SRC_FILES := src/com/android/bluetooth/tests/BluetoothMapContentTest.java
-
-LOCAL_PACKAGE_NAME := BluetoothProfileTests
-
-LOCAL_INSTRUMENTATION_FOR := Bluetooth
-
-include $(BUILD_PACKAGE)
+# Include all makefiles in subdirectories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
new file mode 100644
index 0000000..5a6c0bb
--- /dev/null
+++ b/tests/robotests/Android.mk
@@ -0,0 +1,41 @@
+# Bluetooth Robolectric test target.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# Include the testing libraries (JUnit4 + Robolectric libs).
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    truth-prebuilt \
+    mockito-robolectric-prebuilt
+
+LOCAL_JAVA_LIBRARIES := \
+    junit \
+    platform-robolectric-3.6.1-prebuilt \
+    sdk_vcurrent
+
+LOCAL_INSTRUMENTATION_FOR := Bluetooth
+LOCAL_MODULE := BluetoothRoboTests
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+
+# Bluetooth runner target to run the previous target.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := RunBluetoothRoboTests
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    BluetoothRoboTests
+
+LOCAL_TEST_PACKAGE := Bluetooth
+
+LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src
+
+include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk
diff --git a/tests/robotests/AndroidManifest.xml b/tests/robotests/AndroidManifest.xml
new file mode 100644
index 0000000..25fa624
--- /dev/null
+++ b/tests/robotests/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2017 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    coreApp="true"
+    package="com.android.bluetooth.robotests">
+
+  <application/>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/bluetooth/TestConfig.java b/tests/robotests/src/com/android/bluetooth/TestConfig.java
new file mode 100644
index 0000000..de51f5f
--- /dev/null
+++ b/tests/robotests/src/com/android/bluetooth/TestConfig.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2017 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;
+
+public class TestConfig {
+    public static final int SDK_VERSION = 23;
+    public static final String MANIFEST_PATH =
+            "packages/apps/Bluetooth/tests/robotests/AndroidManifest.xml";
+}
diff --git a/tests/robotests/src/com/android/bluetooth/opp/OppSendFileInfoTest.java b/tests/robotests/src/com/android/bluetooth/opp/OppSendFileInfoTest.java
new file mode 100644
index 0000000..46d9444
--- /dev/null
+++ b/tests/robotests/src/com/android/bluetooth/opp/OppSendFileInfoTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017 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.opp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.bluetooth.TestConfig;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class OppSendFileInfoTest {
+
+    // Constants for test file size.
+    private static final int TEST_FILE_SIZE = 10;
+    private static final int MAXIMUM_FILE_SIZE = 0xFFFFFFFF;
+
+    @Mock Context mContext;
+    @Mock ContentResolver mContentResolver;
+    @Mock FileInputStream mFileInputStream;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Test a BluetoothOppSendFileInfo generated from a local file (MIME type: text/plain,
+     * size: #TEST_FILE_SIZE).
+     * Check whether the BluetoothOppSendFileInfo matches the input.
+     */
+    @Test
+    public void testFileOpen() throws IOException {
+        Uri uri = Uri.parse("file:///android_asset/opp/OppTestFile.txt");
+        doReturn(mContentResolver).when(mContext).getContentResolver();
+        doReturn(mFileInputStream).when(mContentResolver).openInputStream(uri);
+        doReturn(TEST_FILE_SIZE, -1).when(mFileInputStream).read(any(), eq(0), eq(4096));
+        BluetoothOppSendFileInfo sendFileInfo = BluetoothOppSendFileInfo.generateFileInfo(
+                mContext, uri, "text/plain", false);
+        assertThat(sendFileInfo).isNotEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
+        assertThat(sendFileInfo.mFileName).isEqualTo("OppTestFile.txt");
+        assertThat(sendFileInfo.mMimetype).isEqualTo("text/plain");
+        assertThat(sendFileInfo.mLength).isEqualTo(TEST_FILE_SIZE);
+        assertThat(sendFileInfo.mInputStream).isEqualTo(mFileInputStream);
+        assertThat(sendFileInfo.mStatus).isEqualTo(0);
+    }
+
+    /**
+     * Test a BluetoothOppSendFileInfo generated from a web page, which is not supported.
+     * Should return an error BluetoothOppSendFileInfo.
+     */
+    @Test
+    public void testInvalidUriScheme() {
+        Uri webpage = Uri.parse("http://www.android.com/");
+        BluetoothOppSendFileInfo sendFileInfo = BluetoothOppSendFileInfo.generateFileInfo(
+                mContext, webpage, "html", false);
+        assertThat(sendFileInfo).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
+    }
+
+    /**
+     * Test a BluetoothOppSendFileInfo generated from a big local file (MIME type: text/plain,
+     * size: 8GB). It should return an error BluetoothOppSendFileInfo because the maximum size of
+     * file supported is #MAXIMUM_FILE_SIZE (4GB).
+     */
+    @Test
+    public void testBigFileOpen() throws IOException {
+        Uri uri = Uri.parse("file:///android_asset/opp/OppTestFile.txt");
+        doReturn(mContentResolver).when(mContext).getContentResolver();
+        doReturn(mFileInputStream).when(mContentResolver).openInputStream(uri);
+        doReturn(MAXIMUM_FILE_SIZE, MAXIMUM_FILE_SIZE, -1).when(mFileInputStream).read(any(),
+                eq(0), eq(4096));
+        BluetoothOppSendFileInfo sendFileInfo = BluetoothOppSendFileInfo.generateFileInfo(
+                mContext, uri, "text/plain", false);
+        assertThat(sendFileInfo).isEqualTo(BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
+
+    }
+
+}
diff --git a/tests/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
deleted file mode 100644
index 2f18960..0000000
--- a/tests/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2017 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 static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyFloat;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.media.AudioManager;
-import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.test.AndroidTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class A2dpSinkStreamHandlerTest extends AndroidTestCase {
-    static final int DUCK_PERCENT = 75;
-    private HandlerThread mHandlerThread;
-    A2dpSinkStreamHandler streamHandler;
-    ArgumentCaptor<OnAudioFocusChangeListener> audioFocusChangeListenerArgumentCaptor;
-
-    @Mock Context mockContext;
-
-    @Mock A2dpSinkStateMachine mockA2dpSink;
-
-    @Mock AudioManager mockAudioManager;
-
-    @Mock Resources mockResources;
-
-    @Before
-    public void setUp() {
-        // Mock the looper
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        mHandlerThread = new HandlerThread("A2dpSinkStreamHandlerTest");
-        mHandlerThread.start();
-
-        audioFocusChangeListenerArgumentCaptor =
-                ArgumentCaptor.forClass(OnAudioFocusChangeListener.class);
-        when(mockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
-        when(mockContext.getResources()).thenReturn(mockResources);
-        when(mockResources.getInteger(anyInt())).thenReturn(DUCK_PERCENT);
-        when(mockAudioManager.requestAudioFocus(audioFocusChangeListenerArgumentCaptor.capture(),
-                     eq(AudioManager.STREAM_MUSIC), eq(AudioManager.AUDIOFOCUS_GAIN)))
-                .thenReturn(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
-        when(mockAudioManager.abandonAudioFocus(any())).thenReturn(AudioManager.AUDIOFOCUS_GAIN);
-        doNothing().when(mockA2dpSink).informAudioTrackGainNative(anyFloat());
-        when(mockContext.getMainLooper()).thenReturn(mHandlerThread.getLooper());
-
-        streamHandler = spy(new A2dpSinkStreamHandler(mockA2dpSink, mockContext));
-    }
-
-    @Test
-    public void testSrcStart() {
-        // Stream started without local play, expect no change in streaming.
-        streamHandler.handleMessage(
-                streamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START));
-        verify(mockAudioManager, times(0)).requestAudioFocus(any(), anyInt(), anyInt());
-        verify(mockA2dpSink, times(0)).informAudioFocusStateNative(1);
-        verify(mockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
-    }
-
-    @Test
-    public void testSrcStop() {
-        // Stream stopped without local play, expect no change in streaming.
-        streamHandler.handleMessage(
-                streamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP));
-        verify(mockAudioManager, times(0)).requestAudioFocus(any(), anyInt(), anyInt());
-        verify(mockA2dpSink, times(0)).informAudioFocusStateNative(1);
-        verify(mockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
-    }
-
-    @Test
-    public void testSnkPlay() {
-        // Play was pressed locally, expect streaming to start.
-        streamHandler.handleMessage(streamHandler.obtainMessage(A2dpSinkStreamHandler.SNK_PLAY));
-        verify(mockAudioManager, times(1)).requestAudioFocus(any(), anyInt(), anyInt());
-        verify(mockA2dpSink, times(1)).informAudioFocusStateNative(1);
-        verify(mockA2dpSink, times(1)).informAudioTrackGainNative(1.0f);
-    }
-
-    @Test
-    public void testSnkPause() {
-        // Pause was pressed locally, expect streaming to stop.
-        streamHandler.handleMessage(streamHandler.obtainMessage(A2dpSinkStreamHandler.SNK_PAUSE));
-        verify(mockAudioManager, times(0)).requestAudioFocus(any(), anyInt(), anyInt());
-        verify(mockA2dpSink, times(0)).informAudioFocusStateNative(1);
-        verify(mockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
-    }
-
-    @Test
-    public void testDisconnect() {
-        // Remote device was disconnected, expect streaming to stop.
-        testSnkPlay();
-        streamHandler.handleMessage(streamHandler.obtainMessage(A2dpSinkStreamHandler.DISCONNECT));
-        verify(mockAudioManager, times(1)).abandonAudioFocus(any());
-        verify(mockA2dpSink, times(1)).informAudioFocusStateNative(0);
-    }
-
-    @Test
-    public void testSrcPlay() {
-        // Play was pressed remotely, expect no streaming due to lack of audio focus.
-        streamHandler.handleMessage(streamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY));
-        verify(mockAudioManager, times(0)).requestAudioFocus(any(), anyInt(), anyInt());
-        verify(mockA2dpSink, times(0)).informAudioFocusStateNative(1);
-        verify(mockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
-    }
-
-    @Test
-    public void testSrcPause() {
-        // Play was pressed locally, expect streaming to start.
-        streamHandler.handleMessage(streamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY));
-        verify(mockAudioManager, times(0)).requestAudioFocus(any(), anyInt(), anyInt());
-        verify(mockA2dpSink, times(0)).informAudioFocusStateNative(1);
-        verify(mockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
-    }
-
-    @Test
-    public void testFocusGain() {
-        // Focus was gained, expect streaming to resume.
-        testSnkPlay();
-        streamHandler.handleMessage(streamHandler.obtainMessage(
-                A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE, AudioManager.AUDIOFOCUS_GAIN));
-        verify(mockAudioManager, times(1)).requestAudioFocus(any(), anyInt(), anyInt());
-        verify(mockA2dpSink, times(2)).informAudioFocusStateNative(1);
-        verify(mockA2dpSink, times(2)).informAudioTrackGainNative(1.0f);
-    }
-
-    @Test
-    public void testFocusTransientMayDuck() {
-        // TransientMayDuck focus was gained, expect audio stream to duck.
-        testSnkPlay();
-        streamHandler.handleMessage(
-                streamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
-                        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK));
-        verify(mockA2dpSink, times(1)).informAudioTrackGainNative(DUCK_PERCENT / 100.0f);
-    }
-
-    @Test
-    public void testFocusLostTransient() {
-        // Focus was lost transiently, expect streaming to stop.
-        testSnkPlay();
-        streamHandler.handleMessage(streamHandler.obtainMessage(
-                A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
-        verify(mockAudioManager, times(0)).abandonAudioFocus(any());
-        verify(mockA2dpSink, times(1)).informAudioFocusStateNative(0);
-    }
-
-    @Test
-    public void testFocusLost() {
-        // Focus was lost permanently, expect streaming to stop.
-        testSnkPlay();
-        streamHandler.handleMessage(streamHandler.obtainMessage(
-                A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE, AudioManager.AUDIOFOCUS_LOSS));
-        verify(mockAudioManager, times(1)).abandonAudioFocus(any());
-        verify(mockA2dpSink, times(1)).informAudioFocusStateNative(0);
-    }
-}
diff --git a/tests/src/com/android/bluetooth/avrcp/EvictingQueueTest.java b/tests/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
deleted file mode 100644
index 643ce4d..0000000
--- a/tests/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.android.bluetooth.avrcp;
-
-import android.test.AndroidTestCase;
-
-import junit.framework.Assert;
-
-/** Unit tests for {@link EvictingQueue}. */
-public class EvictingQueueTest extends AndroidTestCase {
-    public void testEvictingQueue_canAddItems() {
-        EvictingQueue<Integer> e = new EvictingQueue<Integer>(10);
-
-        e.add(1);
-
-        assertEquals((long) e.size(), (long) 1);
-    }
-
-    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);
-
-        assertEquals((long) e.size(), (long) 5);
-        // Items drop off the front
-        assertEquals((long) e.peek(), (long) 2);
-    }
-
-    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);
-
-        assertEquals((long) e.size(), (long) 5);
-
-        e.addFirst(6);
-
-        assertEquals((long) e.size(), (long) 5);
-        assertEquals((long) e.peek(), (long) 1);
-    }
-}
diff --git a/tests/src/com/android/bluetooth/btservice/PhonePolicyTest.java b/tests/src/com/android/bluetooth/btservice/PhonePolicyTest.java
deleted file mode 100644
index bfdac64..0000000
--- a/tests/src/com/android/bluetooth/btservice/PhonePolicyTest.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2017 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.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.util.Log;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.lang.Thread;
-
-import android.test.AndroidTestCase;
-
-import com.android.bluetooth.hfp.HeadsetService;
-import com.android.bluetooth.a2dp.A2dpService;
-import com.android.bluetooth.btservice.PhonePolicy;
-import com.android.bluetooth.btservice.ServiceFactory;
-import com.android.bluetooth.Utils;
-
-import static org.mockito.Mockito.*;
-
-public class PhonePolicyTest extends AndroidTestCase {
-    private static final String TAG = "PhonePolicyTest";
-    private static final int ASYNC_CALL_TIMEOUT = 2000; // 2s
-    private static final int RETRY_TIMEOUT = 10000; // 10s
-
-    private HandlerThread mHandlerThread;
-    private BluetoothAdapter mAdapter;
-
-    @Override
-    protected void setUp() {
-        mHandlerThread = new HandlerThread("PhonePolicyTest");
-        mHandlerThread.start();
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-    }
-
-    @Override
-    protected void tearDown() {
-        mHandlerThread.quit();
-    }
-
-    // Test that when new UUIDs are refreshed for a device then we set the priorities for various
-    // profiles accurately. The following profiles should have ON priorities:
-    // A2DP, HFP, HID and PAN
-    public void testProcessInitProfilePriorities() {
-        BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
-
-        // Create the mock objects required
-        AdapterService mockAdapterService = mock(AdapterService.class);
-        ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
-        HeadsetService mockHeadsetService = mock(HeadsetService.class);
-        A2dpService mockA2dpService = mock(A2dpService.class);
-
-        // Mock the HeadsetService
-        when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
-        when(mockHeadsetService.getPriority(device))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
-
-        // Mock the A2DP service
-        when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
-        when(mockA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
-
-        // Mock the looper
-        when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
-
-        // Tell the AdapterService that it is a mock (see isMock documentation)
-        when(mockAdapterService.isMock()).thenReturn(true);
-
-        PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
-
-        // Get the broadcast receiver to inject events.
-        BroadcastReceiver injector = phPol.getBroadcastReceiver();
-
-        // Inject an event for UUIDs updated for a remote device with only HFP enabled
-        Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        ParcelUuid[] uuids = new ParcelUuid[2];
-        uuids[0] = BluetoothUuid.Handsfree;
-        uuids[1] = BluetoothUuid.AudioSink;
-
-        intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids);
-        injector.onReceive(null /* context */, intent);
-
-        // Check that the priorities of the devices for preferred profiles are set to ON
-        verify(mockHeadsetService, timeout(ASYNC_CALL_TIMEOUT).times(1))
-                .setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
-        verify(mockA2dpService, timeout(ASYNC_CALL_TIMEOUT).times(1))
-                .setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
-    }
-
-    // Test that when the adapter is turned ON then we call autoconnect on devices that have HFP and
-    // A2DP enabled. NOTE that the assumption is that we have already done the pairing previously
-    // and hence the priorities for the device is already set to AUTO_CONNECT over HFP and A2DP (as
-    // part of post pairing process).
-    public void testAdapterOnAutoConnect() {
-        BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
-
-        // Create the mock objects required
-        AdapterService mockAdapterService = mock(AdapterService.class);
-        ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
-        HeadsetService mockHeadsetService = mock(HeadsetService.class);
-        A2dpService mockA2dpService = mock(A2dpService.class);
-
-        // Return desired values from the mocked object(s)
-        when(mockAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
-        when(mockAdapterService.isQuietModeEnabled()).thenReturn(false);
-        when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
-        when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
-
-        // Return a list of bonded devices (just one)
-        BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
-        bondedDevices[0] = device;
-        when(mockAdapterService.getBondedDevices()).thenReturn(bondedDevices);
-
-        // Return PRIORITY_AUTO_CONNECT over HFP and A2DP
-        when(mockHeadsetService.getPriority(device))
-                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
-        when(mockA2dpService.getPriority(device))
-                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
-
-        // Mock the looper
-        when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
-
-        // Tell the AdapterService that it is a mock (see isMock documentation)
-        when(mockAdapterService.isMock()).thenReturn(true);
-
-        PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
-
-        // Get the broadcast receiver to inject events
-        BroadcastReceiver injector = phPol.getBroadcastReceiver();
-
-        // Inject an event that the adapter is turned on.
-        Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
-        intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
-        injector.onReceive(null /* context */, intent);
-
-        // Check that we got a request to connect over HFP and A2DP
-        verify(mockHeadsetService, timeout(ASYNC_CALL_TIMEOUT).times(1)).connect(eq(device));
-        verify(mockA2dpService, timeout(ASYNC_CALL_TIMEOUT).times(1)).connect(eq(device));
-    }
-
-    // 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
-    public void testReconnectOnPartialConnect() {
-        BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
-
-        // Create the mock objects required
-        AdapterService mockAdapterService = mock(AdapterService.class);
-        ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
-        HeadsetService mockHeadsetService = mock(HeadsetService.class);
-        A2dpService mockA2dpService = mock(A2dpService.class);
-
-        // Setup the mocked factory to return mocked services
-        when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
-        when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
-
-        // Return a list of bonded devices (just one)
-        BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
-        bondedDevices[0] = device;
-        when(mockAdapterService.getBondedDevices()).thenReturn(bondedDevices);
-
-        // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
-        // auto-connectable.
-        when(mockHeadsetService.getPriority(device))
-                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
-        when(mockA2dpService.getPriority(device))
-                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
-
-        when(mockAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
-
-        // Mock the looper
-        when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
-
-        // Tell the AdapterService that it is a mock (see isMock documentation)
-        when(mockAdapterService.isMock()).thenReturn(true);
-
-        PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
-
-        // Get the broadcast receiver to inject events
-        BroadcastReceiver injector = phPol.getBroadcastReceiver();
-
-        // 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, device);
-        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);
-
-        // We should see (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP
-        // To enable that we need to make sure that HeadsetService returns the device as list of
-        // connected devices
-        ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
-        hsConnectedDevices.add(device);
-        when(mockHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
-        // Also the A2DP should say that its not connected for same device
-        when(mockA2dpService.getConnectionState(device))
-                .thenReturn(BluetoothProfile.STATE_DISCONNECTED);
-
-        // Check that we get a call to A2DP connect
-        verify(mockA2dpService, timeout(RETRY_TIMEOUT).times(1)).connect(eq(device));
-    }
-
-    // Test that we will not try to reconnect on a profile if all the connections failed
-    public void testNoReconnectOnNoConnect() {
-        BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
-
-        // Create the mock objects required
-        AdapterService mockAdapterService = mock(AdapterService.class);
-        ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
-        HeadsetService mockHeadsetService = mock(HeadsetService.class);
-        A2dpService mockA2dpService = mock(A2dpService.class);
-
-        // Setup the mocked factory to return mocked services
-        when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
-        when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
-
-        // Return a list of bonded devices (just one)
-        BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
-        bondedDevices[0] = device;
-        when(mockAdapterService.getBondedDevices()).thenReturn(bondedDevices);
-
-        // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
-        // auto-connectable.
-        when(mockHeadsetService.getPriority(device))
-                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
-        when(mockA2dpService.getPriority(device))
-                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
-
-        when(mockAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
-
-        // Mock the looper
-        when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
-
-        // Tell the AdapterService that it is a mock (see isMock documentation)
-        when(mockAdapterService.isMock()).thenReturn(true);
-
-        PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
-
-        // Get the broadcast receiver to inject events
-        BroadcastReceiver injector = phPol.getBroadcastReceiver();
-
-        // 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, device);
-        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);
-
-        // Return an empty list simulating that the above connection successful was nullified
-        ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
-        when(mockHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
-
-        // Also the A2DP should say that its not connected for same device
-        when(mockA2dpService.getConnectionState(device))
-                .thenReturn(BluetoothProfile.STATE_DISCONNECTED);
-
-        // To check that we have processed all the messages we need to have a hard sleep here. The
-        // reason being mockito can only verify synchronous calls, asynchronous calls are hidden
-        // from its mocking framework. Also, Looper does not provide a way to wait until all future
-        // messages are proceed.
-        try {
-            Thread.sleep(RETRY_TIMEOUT);
-        } catch (Exception ex) {
-        }
-
-        // Check that we don't get any calls to reconnect
-        verify(mockA2dpService, never()).connect(eq(device));
-        verify(mockHeadsetService, never()).connect(eq(device));
-    }
-
-    // Test that a device with no supported uuids is initialized properly and does not crash the
-    // stack
-    public void testNoSupportedUuids() {
-        BluetoothAdapter inst = BluetoothAdapter.getDefaultAdapter();
-        BluetoothDevice device = inst.getRemoteDevice("00:01:02:03:04:05");
-
-        // Create the mock objects required
-        AdapterService mockAdapterService = mock(AdapterService.class);
-        ServiceFactory mockServiceFactory = mock(ServiceFactory.class);
-        HeadsetService mockHeadsetService = mock(HeadsetService.class);
-        A2dpService mockA2dpService = mock(A2dpService.class);
-
-        // Mock the HeadsetService
-        when(mockServiceFactory.getHeadsetService()).thenReturn(mockHeadsetService);
-        when(mockHeadsetService.getPriority(device))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
-
-        // Mock the A2DP service
-        when(mockServiceFactory.getA2dpService()).thenReturn(mockA2dpService);
-        when(mockA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
-
-        // Mock the looper
-        when(mockAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
-
-        PhonePolicy phPol = new PhonePolicy(mockAdapterService, mockServiceFactory);
-
-        // Get the broadcast receiver to inject events.
-        BroadcastReceiver injector = phPol.getBroadcastReceiver();
-
-        // Inject an event for UUIDs updated for a remote device with only HFP enabled
-        Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-
-        // Put no UUIDs
-        injector.onReceive(null /* context */, intent);
-
-        // To check that we have processed all the messages we need to have a hard sleep here. The
-        // reason being mockito can only verify synchronous calls, asynchronous calls are hidden
-        // from its mocking framework. Also, Looper does not provide a way to wait until all future
-        // messages are proceed.
-        try {
-            Thread.sleep(RETRY_TIMEOUT);
-        } catch (Exception ex) {
-        }
-
-        // Check that we do not crash and not call any setPriority methods
-        verify(mockHeadsetService, never())
-                .setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
-        verify(mockA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
-    }
-}
diff --git a/tests/src/com/android/bluetooth/core/FileSystemWriteTest.java b/tests/src/com/android/bluetooth/core/FileSystemWriteTest.java
deleted file mode 100644
index bedce28..0000000
--- a/tests/src/com/android/bluetooth/core/FileSystemWriteTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.android.bluetooth;
-
-import android.test.AndroidTestCase;
-import java.io.IOException;
-import java.io.File;
-
-// Test Bluetooth's ability to write to the different directories that it
-// is supposed to own
-public class FileSystemWriteTest extends AndroidTestCase {
-    public void testBluetoothDirWrite() {
-        try {
-            File file = new File("/data/misc/bluetooth/test.file");
-            assertTrue("File not created", file.createNewFile());
-            file.delete();
-        } catch (IOException e) {
-            fail("Exception creating file /data/misc/bluetooth/test.file: " + e);
-        }
-    }
-
-    public void testBluedroidDirWrite() {
-        try {
-            File file = new File("/data/misc/bluedroid/test.file");
-            assertTrue("File not created", file.createNewFile());
-            file.delete();
-        } catch (IOException e) {
-            fail("Exception creating file /data/misc/bluedroid/test.file: " + e);
-        }
-    }
-
-    public void testBluetoothLogsDirWrite() {
-        try {
-            File file = new File("/data/misc/bluetooth/logs/test.file");
-            assertTrue("File not created", file.createNewFile());
-            file.delete();
-        } catch (IOException e) {
-            fail("Exception creating file /data/misc/bluetooth/logs/test.file: " + e);
-        }
-    }
-}
diff --git a/tests/src/com/android/bluetooth/gatt/GattServiceTest.java b/tests/src/com/android/bluetooth/gatt/GattServiceTest.java
deleted file mode 100644
index 28c5a10..0000000
--- a/tests/src/com/android/bluetooth/gatt/GattServiceTest.java
+++ /dev/null
@@ -1,22 +0,0 @@
-
-package com.android.bluetooth.gatt;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.bluetooth.gatt.GattService;
-
-/**
- * Test cases for {@link GattService}.
- */
-public class GattServiceTest extends AndroidTestCase {
-
-    @SmallTest
-    public void testParseBatchTimestamp() {
-        GattService service = new GattService();
-        long timestampNanos = service.parseTimestampNanos(new byte[] {
-                -54, 7 });
-        assertEquals(99700000000L, timestampNanos);
-    }
-
-}
diff --git a/tests/src/com/android/bluetooth/hdp/HealthServiceTest.java b/tests/src/com/android/bluetooth/hdp/HealthServiceTest.java
deleted file mode 100644
index 1aaaa9c..0000000
--- a/tests/src/com/android/bluetooth/hdp/HealthServiceTest.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.android.bluetooth.hdp;
-
-import android.test.InstrumentationTestCase;
-
-import junit.framework.Assert;
-
-public class HealthServiceTest extends InstrumentationTestCase {
-
-    public void testRegisterAppConfiguration() {
-        HealthService testService = new HealthService();
-
-        // Attach a context to the Health Service for permission checks
-        testService.attach(getInstrumentation().getContext(), null, null, null, null, null);
-
-        // Test registering a null config
-        assertEquals(false, testService.registerAppConfiguration(null, null));
-    }
-
-}
diff --git a/tests/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java b/tests/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
deleted file mode 100644
index 70825ff..0000000
--- a/tests/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.android.bluetooth.hfpclient;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import com.android.bluetooth.btservice.AdapterService;
-
-import static org.mockito.Mockito.*;
-import org.mockito.ArgumentCaptor;
-
-public class HeadsetClientServiceTest extends AndroidTestCase {
-    // Time to wait for the service to be initialized
-    private static int SERVICE_START_TIMEOUT_MS = 5000;  // 5 sec
-    private static int STATE_MACHINE_TRANSITION_TIMEOUT_MS = 5000;  // 5 sec
-    private HeadsetClientService mService = null;
-    private BluetoothAdapter mAdapter = null;
-
-    void startServices() {
-        Intent startIntent = new Intent(getContext(), HeadsetClientService.class);
-        getContext().startService(startIntent);
-
-        try {
-            Thread.sleep(SERVICE_START_TIMEOUT_MS);
-        } catch (Exception ex) {}
-
-        // At this point the service should have started so check NOT null
-        mService = HeadsetClientService.getHeadsetClientService();
-        assertTrue(mService != null);
-
-        // At this point Adapter Service should have started
-        AdapterService inst = mock(AdapterService.class);
-        assertTrue(inst != null);
-
-        // Try getting the Bluetooth adapter
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        assertTrue(mAdapter != null);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        startServices();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mService = null;
-        mAdapter = null;
-    }
-
-    // Test that we can initialize the service
-    public void testInitialize() {
-    }
-}
diff --git a/tests/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/tests/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
deleted file mode 100644
index 68adb29..0000000
--- a/tests/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-package com.android.bluetooth.hfpclient;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.os.Bundle;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Arrays;
-import java.util.ArrayList;
-
-import com.android.bluetooth.btservice.AdapterService;
-
-import static org.mockito.Mockito.*;
-
-import org.mockito.ArgumentCaptor;
-
-public class HeadsetClientStateMachineTest extends AndroidTestCase {
-    private BluetoothAdapter mAdapter = null;
-
-    @Override
-    protected void setUp() throws Exception {
-        AdapterService inst = mock(AdapterService.class);
-        assertTrue(inst != null);
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-    }
-
-    // Test that default state is disconnected
-    public void testDefaultDisconnectedState() {
-        HeadsetClientService mockService = mock(HeadsetClientService.class);
-        AudioManager mockAudioManager = mock(AudioManager.class);
-
-        when(mockService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
-
-        HeadsetClientStateMachine mockSM = new HeadsetClientStateMachine(
-            mockService, getContext().getMainLooper());
-        assertEquals(
-            mockSM.getConnectionState((BluetoothDevice) null), BluetoothProfile.STATE_DISCONNECTED);
-    }
-
-    // Test that an incoming connection with low priority is rejected
-    public void testIncomingPriorityReject() {
-        HeadsetClientService mockService = mock(HeadsetClientService.class);
-        AudioManager mockAudioManager = mock(AudioManager.class);
-        BluetoothDevice device = mAdapter.getRemoteDevice("00:01:02:03:04:05");
-
-        when(mockService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
-
-        HeadsetClientStateMachine mockSM = new HeadsetClientStateMachine(
-            mockService, getContext().getMainLooper());
-        mockSM.start();
-
-        // Return false for priority.
-        when(mockService.getPriority(any(BluetoothDevice.class))).thenReturn(
-            BluetoothProfile.PRIORITY_OFF);
-
-        // Inject an event for when incoming connection is requested
-        StackEvent connStCh =
-            new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
-        connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
-        connStCh.valueInt2 = 0;
-        connStCh.valueInt3 = 0;
-        connStCh.device = device;
-        mockSM.sendMessage(StackEvent.STACK_EVENT, connStCh);
-
-        // Verify that no connection state broadcast is executed
-        verify(mockService, never()).sendBroadcast(any(Intent.class), anyString());
-        // Check we are in disconnected state still.
-        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Disconnected);
-    }
-
-    // Test that an incoming connection with high priority is accepted
-    public void testIncomingPriorityAccept() {
-        HeadsetClientService mockService = mock(HeadsetClientService.class);
-        AudioManager mockAudioManager = mock(AudioManager.class);
-        BluetoothDevice device = mAdapter.getRemoteDevice("00:01:02:03:04:05");
-
-        when(mockService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
-        // Set a valid volume
-        when(mockAudioManager.getStreamVolume(anyInt())).thenReturn(2);
-        when(mockAudioManager.getStreamMaxVolume(anyInt())).thenReturn(10);
-        when(mockAudioManager.getStreamMinVolume(anyInt())).thenReturn(1);
-
-
-        HeadsetClientStateMachine mockSM = new HeadsetClientStateMachine(
-            mockService, getContext().getMainLooper());
-        mockSM.start();
-
-        // Return false for priority.
-        when(mockService.getPriority(any(BluetoothDevice.class))).thenReturn(
-            BluetoothProfile.PRIORITY_ON);
-
-        // Inject an event for when incoming connection is requested
-        StackEvent connStCh =
-            new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
-        connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
-        connStCh.valueInt2 = 0;
-        connStCh.valueInt3 = 0;
-        connStCh.device = device;
-        mockSM.sendMessage(StackEvent.STACK_EVENT, connStCh);
-
-        // Verify that one connection state broadcast is executed
-        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
-        verify(mockService,
-            timeout(1000)).sendBroadcast(intentArgument1.capture(), anyString());
-        assertEquals(BluetoothProfile.STATE_CONNECTING,
-            intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
-
-        // Check we are in connecting state now.
-        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Connecting);
-
-        // Send a message to 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.valueInt3 = 0;
-        slcEvent.device = device;
-        mockSM.sendMessage(StackEvent.STACK_EVENT, slcEvent);
-
-        // Verify that one connection state broadcast is executed
-        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
-        verify(mockService,
-            timeout(1000).times(2)).sendBroadcast(intentArgument2.capture(), anyString());
-        assertEquals(BluetoothProfile.STATE_CONNECTED,
-            intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
-        // Check we are in connecting state now.
-        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Connected);
-    }
-
-    // Test that an incoming connection that times out
-    public void testIncomingTimeout() {
-        HeadsetClientService mockService = mock(HeadsetClientService.class);
-        AudioManager mockAudioManager = mock(AudioManager.class);
-        BluetoothDevice device = mAdapter.getRemoteDevice("00:01:02:03:04:05");
-
-        when(mockService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
-        // Set a valid volume
-        when(mockAudioManager.getStreamVolume(anyInt())).thenReturn(2);
-        when(mockAudioManager.getStreamMaxVolume(anyInt())).thenReturn(10);
-        when(mockAudioManager.getStreamMinVolume(anyInt())).thenReturn(1);
-
-
-        HeadsetClientStateMachine mockSM = new HeadsetClientStateMachine(
-            mockService, getContext().getMainLooper());
-        mockSM.start();
-
-        // Return false for priority.
-        when(mockService.getPriority(any(BluetoothDevice.class))).thenReturn(
-            BluetoothProfile.PRIORITY_ON);
-
-        // Inject an event for when incoming connection is requested
-        StackEvent connStCh =
-            new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
-        connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
-        connStCh.valueInt2 = 0;
-        connStCh.valueInt3 = 0;
-        connStCh.device = device;
-        mockSM.sendMessage(StackEvent.STACK_EVENT, connStCh);
-
-        // Verify that one connection state broadcast is executed
-        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
-        verify(mockService, timeout(1000)).sendBroadcast(intentArgument1.capture(), anyString());
-        assertEquals(BluetoothProfile.STATE_CONNECTING,
-            intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
-
-        // Check we are in connecting state now.
-        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Connecting);
-
-        // Verify that one connection state broadcast is executed
-        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
-        verify(mockService,
-            timeout(HeadsetClientStateMachine.CONNECTING_TIMEOUT_MS * 2).times(2)).sendBroadcast(
-            intentArgument2.capture(), anyString());
-        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
-            intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
-
-        // Check we are in connecting state now.
-        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Disconnected);
-    }
-}
diff --git a/tests/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java b/tests/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
deleted file mode 100644
index 4b185ee..0000000
--- a/tests/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.android.bluetooth.map;
-
-import android.content.Context;
-import android.content.ContentResolver;
-import android.content.ContentProvider;
-import android.database.sqlite.SQLiteException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.provider.Telephony.Sms;
-import android.test.AndroidTestCase;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockContentProvider;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class BluetoothMapContentObserverTest extends AndroidTestCase {
-
-    class ExceptionTestProvider extends MockContentProvider {
-        public ExceptionTestProvider(Context context) {
-            super(context);
-        }
-
-        @Override
-        public Cursor query(Uri uri, String[] b, String s, String[] c, String d) {
-            throw new SQLiteException();
-        }
-    }
-
-    public void testInitMsgList() {
-        if (Looper.myLooper() == null) Looper.prepare();
-
-        Context mockContext = mock(Context.class);
-        MockContentResolver mockResolver = new MockContentResolver();
-        ExceptionTestProvider mockProvider = new ExceptionTestProvider(mockContext);
-        mockResolver.addProvider("sms", mockProvider);
-
-        TelephonyManager mockTelephony = mock(TelephonyManager.class);
-        UserManager mockUserService = mock(UserManager.class);
-        BluetoothMapMasInstance mockMas = mock(BluetoothMapMasInstance.class);
-
-        // Functions that get called when BluetoothMapContentObserver is created
-        when(mockUserService.isUserUnlocked()).thenReturn(true);
-        when(mockContext.getContentResolver()).thenReturn(mockResolver);
-        when(mockContext.getSystemService(Context.TELEPHONY_SERVICE))
-            .thenReturn(mockTelephony);
-        when(mockContext.getSystemService(Context.USER_SERVICE))
-            .thenReturn(mockUserService);
-
-        BluetoothMapContentObserver observer;
-        try {
-            // The constructor of BluetoothMapContentObserver calls initMsgList
-            observer = new BluetoothMapContentObserver(mockContext, null,
-                                                       mockMas, null, true);
-        } catch(RemoteException e) {
-            fail("Failed to created BluetoothMapContentObserver object");
-        } catch(SQLiteException e) {
-            fail("Threw SQLiteException instead of failing cleanly");
-        }
-    }
-}
diff --git a/tests/src/com/android/bluetooth/pbapclient/PbapParserTest.java b/tests/src/com/android/bluetooth/pbapclient/PbapParserTest.java
deleted file mode 100644
index 93b02b3..0000000
--- a/tests/src/com/android/bluetooth/pbapclient/PbapParserTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2017 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.accounts.Account;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.provider.CallLog.Calls;
-import android.test.AndroidTestCase;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class PbapParserTest extends AndroidTestCase {
-    private Account mAccount;
-    private Resources testResources;
-    private static final String mTestAccountName = "PBAPTESTACCOUNT";
-    private static final String mTestPackageName = "com.android.bluetooth.tests";
-
-    @Before
-    public void setUp() {
-        mAccount = new Account(mTestAccountName,
-                mContext.getString(com.android.bluetooth.R.string.pbap_account_type));
-        try {
-            testResources =
-                    mContext.getPackageManager().getResourcesForApplication(mTestPackageName);
-        } catch (Exception e) {
-            fail("Setup Failure Unable to get resources" + e.toString());
-        }
-    }
-
-    // testNoTimestamp should parse 1 poorly formed vcard and not crash.
-    @Test
-    public void testNoTimestamp() throws IOException {
-        InputStream fileStream;
-        fileStream = testResources.openRawResource(
-                com.android.bluetooth.tests.R.raw.no_timestamp_call_log);
-        BluetoothPbapVcardList pbapVCardList = new BluetoothPbapVcardList(
-                mAccount, fileStream, PbapClientConnectionHandler.VCARD_TYPE_30);
-        assertEquals(1, pbapVCardList.getCount());
-        CallLogPullRequest processor =
-                new CallLogPullRequest(mContext, PbapClientConnectionHandler.MCH_PATH);
-        processor.setResults(pbapVCardList.getList());
-
-        // Verify that these entries aren't in the call log to start.
-        assertFalse(verifyCallLog("555-0001", null, "3"));
-
-        // Finish processing the data and verify entries were added to the call log.
-        processor.onPullComplete();
-        assertTrue(verifyCallLog("555-0001", null, "3"));
-    }
-
-    // testMissedCall should parse one phonecall correctly.
-    @Test
-    public void testMissedCall() throws IOException {
-        InputStream fileStream;
-        fileStream =
-                testResources.openRawResource(com.android.bluetooth.tests.R.raw.single_missed_call);
-        BluetoothPbapVcardList pbapVCardList = new BluetoothPbapVcardList(
-                mAccount, fileStream, PbapClientConnectionHandler.VCARD_TYPE_30);
-        assertEquals(1, pbapVCardList.getCount());
-        CallLogPullRequest processor =
-                new CallLogPullRequest(mContext, PbapClientConnectionHandler.MCH_PATH);
-        processor.setResults(pbapVCardList.getList());
-
-        // Verify that these entries aren't in the call log to start.
-        // EST is default Time Zone
-        assertFalse(verifyCallLog("555-0002", "1483250460000", "3"));
-        // Finish processing the data and verify entries were added to the call log.
-        processor.onPullComplete();
-        assertTrue(verifyCallLog("555-0002", "1483250460000", "3"));
-    }
-
-    // testUnknownCall should parse two calls with no phone number.
-    @Test
-    public void testUnknownCall() throws IOException {
-        InputStream fileStream;
-        fileStream = testResources.openRawResource(
-                com.android.bluetooth.tests.R.raw.unknown_number_call);
-        BluetoothPbapVcardList pbapVCardList = new BluetoothPbapVcardList(
-                mAccount, fileStream, PbapClientConnectionHandler.VCARD_TYPE_30);
-        assertEquals(2, pbapVCardList.getCount());
-        CallLogPullRequest processor =
-                new CallLogPullRequest(mContext, PbapClientConnectionHandler.MCH_PATH);
-        processor.setResults(pbapVCardList.getList());
-
-        // Verify that these entries aren't in the call log to start.
-        // EST is default Time Zone
-        assertFalse(verifyCallLog("", "1483250520000", "3"));
-        assertFalse(verifyCallLog("", "1483250580000", "3"));
-
-        // Finish processing the data and verify entries were added to the call log.
-        processor.onPullComplete();
-        assertTrue(verifyCallLog("", "1483250520000", "3"));
-        assertTrue(verifyCallLog("", "1483250580000", "3"));
-    }
-
-    // Find Entries in call log with type matching number and date.
-    // If number or date is null it will match any number or date respectively.
-    boolean verifyCallLog(String number, String date, String type) {
-        String[] query = new String[] {Calls.NUMBER, Calls.DATE, Calls.TYPE};
-        Cursor cursor = mContext.getContentResolver().query(Calls.CONTENT_URI, query,
-                Calls.TYPE + "= " + type, null, Calls.DATE + ", " + Calls.NUMBER);
-        if (cursor != null) {
-            while (cursor.moveToNext()) {
-                String foundNumber = cursor.getString(cursor.getColumnIndex(Calls.NUMBER));
-                String foundDate = cursor.getString(cursor.getColumnIndex(Calls.DATE));
-                if ((number == null || number.equals(foundNumber))
-                        && (date == null || date.equals(foundDate))) {
-                    return true;
-                }
-            }
-            cursor.close();
-        }
-        return false;
-    }
-}
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
new file mode 100644
index 0000000..ef948a6
--- /dev/null
+++ b/tests/unit/Android.mk
@@ -0,0 +1,32 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := platform
+
+LOCAL_JAVA_LIBRARIES := \
+    javax.obex \
+    android.test.runner \
+    telephony-common \
+    libprotobuf-java-micro \
+    android.test.base \
+    android.test.mock
+
+LOCAL_STATIC_JAVA_LIBRARIES :=  \
+    com.android.emailcommon \
+    android-support-test \
+    mockito-target \
+    espresso-intents
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := BluetoothInstrumentationTests
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_INSTRUMENTATION_FOR := Bluetooth
+
+include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
old mode 100755
new mode 100644
similarity index 97%
rename from tests/AndroidManifest.xml
rename to tests/unit/AndroidManifest.xml
index 142d0f2..e344399
--- a/tests/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -55,6 +55,7 @@
         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"
diff --git a/tests/unit/AndroidTest.xml b/tests/unit/AndroidTest.xml
new file mode 100644
index 0000000..230f2b1
--- /dev/null
+++ b/tests/unit/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 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.
+-->
+<configuration description="Runs Bluetooth Test Cases.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="BluetoothInstrumentationTests.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="settings put global ble_scan_always_enabled 0" />
+        <option name="run-command" value="svc bluetooth disable" />
+        <option name="teardown-command" value="svc bluetooth enable" />
+        <option name="teardown-command" value="settings put global ble_scan_always_enabled 1" />
+    </target_preparer>
+
+    <option name="test-tag" value="BluetoothInstrumentationTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.bluetooth.tests" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/tests/res/raw/no_timestamp_call_log.vcf b/tests/unit/res/raw/no_timestamp_call_log.vcf
similarity index 100%
rename from tests/res/raw/no_timestamp_call_log.vcf
rename to tests/unit/res/raw/no_timestamp_call_log.vcf
diff --git a/tests/res/raw/single_missed_call.vcf b/tests/unit/res/raw/single_missed_call.vcf
similarity index 100%
rename from tests/res/raw/single_missed_call.vcf
rename to tests/unit/res/raw/single_missed_call.vcf
diff --git a/tests/res/raw/unknown_number_call.vcf b/tests/unit/res/raw/unknown_number_call.vcf
similarity index 100%
rename from tests/res/raw/unknown_number_call.vcf
rename to tests/unit/res/raw/unknown_number_call.vcf
diff --git a/tests/unit/res/raw/v30_simple.vcf b/tests/unit/res/raw/v30_simple.vcf
new file mode 100644
index 0000000..227f43c
--- /dev/null
+++ b/tests/unit/res/raw/v30_simple.vcf
@@ -0,0 +1,13 @@
+BEGIN:VCARD
+VERSION:3.0
+FN:And Roid
+N:And;Roid;;;
+ORG:Open;Handset; Alliance
+SORT-STRING:android
+TEL;TYPE=PREF;TYPE=VOICE:0300000000
+CLASS:PUBLIC
+X-GNO:0
+X-GN:group0
+X-REDUCTION:0
+REV:20081031T065854Z
+END:VCARD
diff --git a/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java b/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java
new file mode 100644
index 0000000..7841e80
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/FileSystemWriteTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 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;
+
+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.io.File;
+import java.io.IOException;
+
+/**
+ * Test Bluetooth's ability to write to the different directories that it
+ * is supposed to own
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class FileSystemWriteTest {
+    @Test
+    public void testBluetoothDirWrite() {
+        try {
+            File file = new File("/data/misc/bluetooth/test.file");
+            Assert.assertTrue("File not created", file.createNewFile());
+            file.delete();
+        } catch (IOException e) {
+            Assert.fail("Exception creating file /data/misc/bluetooth/test.file: " + e);
+        }
+    }
+
+    @Test
+    public void testBluedroidDirWrite() {
+        try {
+            File file = new File("/data/misc/bluedroid/test.file");
+            Assert.assertTrue("File not created", file.createNewFile());
+            file.delete();
+        } catch (IOException e) {
+            Assert.fail("Exception creating file /data/misc/bluedroid/test.file: " + e);
+        }
+    }
+
+    @Test
+    public void testBluetoothLogsDirWrite() {
+        try {
+            File file = new File("/data/misc/bluetooth/logs/test.file");
+            Assert.assertTrue("File not created", file.createNewFile());
+            file.delete();
+        } catch (IOException e) {
+            Assert.fail("Exception creating file /data/misc/bluetooth/logs/test.file: " + e);
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/TestUtils.java b/tests/unit/src/com/android/bluetooth/TestUtils.java
new file mode 100644
index 0000000..d0e3324
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/TestUtils.java
@@ -0,0 +1,304 @@
+/*
+ * 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;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ServiceTestRule;
+
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+
+import org.junit.Assert;
+import org.mockito.ArgumentCaptor;
+import org.mockito.internal.util.MockUtil;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A set of methods useful in Bluetooth instrumentation tests
+ */
+public class TestUtils {
+    private static final int SERVICE_TOGGLE_TIMEOUT_MS = 1000;    // 1s
+
+    /**
+     * Utility method to replace obj.fieldName with newValue where obj is of type c
+     *
+     * @param c type of obj
+     * @param fieldName field name to be replaced
+     * @param obj instance of type c whose fieldName is to be replaced, null for static fields
+     * @param newValue object used to replace fieldName
+     * @return the old value of fieldName that got replaced, caller is responsible for restoring
+     *         it back to obj
+     * @throws NoSuchFieldException when fieldName is not found in type c
+     * @throws IllegalAccessException when fieldName cannot be accessed in type c
+     */
+    public static Object replaceField(final Class c, final String fieldName, final Object obj,
+            final Object newValue) throws NoSuchFieldException, IllegalAccessException {
+        Field field = c.getDeclaredField(fieldName);
+        field.setAccessible(true);
+
+        Object oldValue = field.get(obj);
+        field.set(obj, newValue);
+        return oldValue;
+    }
+
+    /**
+     * Set the return value of {@link AdapterService#getAdapterService()} to a test specified value
+     *
+     * @param adapterService the designated {@link AdapterService} in test, must not be null, can
+     * be mocked or spied
+     * @throws NoSuchMethodException when setAdapterService method is not found
+     * @throws IllegalAccessException when setAdapterService method cannot be accessed
+     * @throws InvocationTargetException when setAdapterService method cannot be invoked, which
+     * should never happen since setAdapterService is a static method
+     */
+    public static void setAdapterService(AdapterService adapterService)
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+        Assert.assertNull("AdapterService.getAdapterService() must be null before setting another"
+                + " AdapterService", AdapterService.getAdapterService());
+        Assert.assertNotNull(adapterService);
+        // We cannot mock AdapterService.getAdapterService() with Mockito.
+        // Hence we need to use reflection to call a private method to
+        // initialize properly the AdapterService.sAdapterService field.
+        Method method =
+                AdapterService.class.getDeclaredMethod("setAdapterService", AdapterService.class);
+        method.setAccessible(true);
+        method.invoke(null, adapterService);
+    }
+
+    /**
+     * Clear the return value of {@link AdapterService#getAdapterService()} to null
+     *
+     * @param adapterService the {@link AdapterService} used when calling
+     * {@link TestUtils#setAdapterService(AdapterService)}
+     * @throws NoSuchMethodException when clearAdapterService method is not found
+     * @throws IllegalAccessException when clearAdapterService method cannot be accessed
+     * @throws InvocationTargetException when clearAdappterService method cannot be invoked,
+     * which should never happen since clearAdapterService is a static method
+     */
+    public static void clearAdapterService(AdapterService adapterService)
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+        Assert.assertSame("AdapterService.getAdapterService() must return the same object as the"
+                        + " supplied adapterService in this method", adapterService,
+                AdapterService.getAdapterService());
+        Assert.assertNotNull(adapterService);
+        Method method =
+                AdapterService.class.getDeclaredMethod("clearAdapterService", AdapterService.class);
+        method.setAccessible(true);
+        method.invoke(null, adapterService);
+    }
+
+    /**
+     * Start a profile service using the given {@link ServiceTestRule} and verify through
+     * {@link AdapterService#getAdapterService()} that the service is actually started within
+     * {@link TestUtils#SERVICE_TOGGLE_TIMEOUT_MS} milliseconds.
+     * {@link #setAdapterService(AdapterService)} must be called with a mocked
+     * {@link AdapterService} before calling this method
+     *
+     * @param serviceTestRule the {@link ServiceTestRule} used to execute the service start request
+     * @param profileServiceClass a class from one of {@link ProfileService}'s child classes
+     * @throws TimeoutException when service failed to start within either default timeout of
+     * {@link ServiceTestRule#DEFAULT_TIMEOUT} (normally 5s) or user specified time when creating
+     * {@link ServiceTestRule} through {@link ServiceTestRule#withTimeout(long, TimeUnit)} method
+     */
+    public static <T extends ProfileService> void startService(ServiceTestRule serviceTestRule,
+            Class<T> profileServiceClass) throws TimeoutException {
+        AdapterService adapterService = AdapterService.getAdapterService();
+        Assert.assertNotNull(adapterService);
+        Assert.assertTrue("AdapterService.getAdapterService() must return a mocked or spied object"
+                + " before calling this method", MockUtil.isMock(adapterService));
+        Intent startIntent =
+                new Intent(InstrumentationRegistry.getTargetContext(), profileServiceClass);
+        startIntent.putExtra(AdapterService.EXTRA_ACTION,
+                AdapterService.ACTION_SERVICE_STATE_CHANGED);
+        startIntent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
+        serviceTestRule.startService(startIntent);
+        ArgumentCaptor<ProfileService> profile = ArgumentCaptor.forClass(profileServiceClass);
+        verify(adapterService, timeout(SERVICE_TOGGLE_TIMEOUT_MS)).onProfileServiceStateChanged(
+                profile.capture(), eq(BluetoothAdapter.STATE_ON));
+        Assert.assertEquals(profileServiceClass.getName(), profile.getValue().getClass().getName());
+    }
+
+    /**
+     * Stop a profile service using the given {@link ServiceTestRule} and verify through
+     * {@link AdapterService#getAdapterService()} that the service is actually stopped within
+     * {@link TestUtils#SERVICE_TOGGLE_TIMEOUT_MS} milliseconds.
+     * {@link #setAdapterService(AdapterService)} must be called with a mocked
+     * {@link AdapterService} before calling this method
+     *
+     * @param serviceTestRule the {@link ServiceTestRule} used to execute the service start request
+     * @param profileServiceClass a class from one of {@link ProfileService}'s child classes
+     * @throws TimeoutException when service failed to start within either default timeout of
+     * {@link ServiceTestRule#DEFAULT_TIMEOUT} (normally 5s) or user specified time when creating
+     * {@link ServiceTestRule} through {@link ServiceTestRule#withTimeout(long, TimeUnit)} method
+     */
+    public static <T extends ProfileService> void stopService(ServiceTestRule serviceTestRule,
+            Class<T> profileServiceClass) throws TimeoutException {
+        AdapterService adapterService = AdapterService.getAdapterService();
+        Assert.assertNotNull(adapterService);
+        Assert.assertTrue("AdapterService.getAdapterService() must return a mocked or spied object"
+                + " before calling this method", MockUtil.isMock(adapterService));
+        Intent stopIntent =
+                new Intent(InstrumentationRegistry.getTargetContext(), profileServiceClass);
+        stopIntent.putExtra(AdapterService.EXTRA_ACTION,
+                AdapterService.ACTION_SERVICE_STATE_CHANGED);
+        stopIntent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
+        serviceTestRule.startService(stopIntent);
+        ArgumentCaptor<ProfileService> profile = ArgumentCaptor.forClass(profileServiceClass);
+        verify(adapterService, timeout(SERVICE_TOGGLE_TIMEOUT_MS)).onProfileServiceStateChanged(
+                profile.capture(), eq(BluetoothAdapter.STATE_OFF));
+        Assert.assertEquals(profileServiceClass.getName(), profile.getValue().getClass().getName());
+    }
+
+    /**
+     * Create a test device.
+     *
+     * @param bluetoothAdapter the Bluetooth adapter to use
+     * @param id the test device ID. It must be an integer in the interval [0, 0xFF].
+     * @return {@link BluetoothDevice} test device for the device ID
+     */
+    public static BluetoothDevice getTestDevice(BluetoothAdapter bluetoothAdapter, int id) {
+        Assert.assertTrue(id <= 0xFF);
+        Assert.assertNotNull(bluetoothAdapter);
+        BluetoothDevice testDevice =
+                bluetoothAdapter.getRemoteDevice(String.format("00:01:02:03:04:%02X", id));
+        Assert.assertNotNull(testDevice);
+        return testDevice;
+    }
+
+    /**
+     * Wait and verify that an intent has been received.
+     *
+     * @param timeoutMs the time (in milliseconds) to wait for the intent
+     * @param queue the queue for the intent
+     * @return the received intent
+     */
+    public static Intent waitForIntent(int timeoutMs, BlockingQueue<Intent> queue) {
+        try {
+            Intent intent = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
+            Assert.assertNotNull(intent);
+            return intent;
+        } catch (InterruptedException e) {
+            Assert.fail("Cannot obtain an Intent from the queue: " + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Wait and verify that no intent has been received.
+     *
+     * @param timeoutMs the time (in milliseconds) to wait and verify no intent
+     * has been received
+     * @param queue the queue for the intent
+     * @return the received intent. Should be null under normal circumstances
+     */
+    public static Intent waitForNoIntent(int timeoutMs, BlockingQueue<Intent> queue) {
+        try {
+            Intent intent = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
+            Assert.assertNull(intent);
+            return intent;
+        } catch (InterruptedException e) {
+            Assert.fail("Cannot obtain an Intent from the queue: " + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Wait for looper to finish its current task and all tasks schedule before this
+     *
+     * @param looper looper of interest
+     */
+    public static void waitForLooperToFinishScheduledTask(Looper looper) {
+        runOnLooperSync(looper, () -> {
+            // do nothing, just need to make sure looper finishes current task
+        });
+    }
+
+    /**
+     * Run synchronously a runnable action on a looper.
+     * The method will return after the action has been execution to completion.
+     *
+     * Example:
+     * <pre>
+     * {@code
+     * TestUtils.runOnMainSync(new Runnable() {
+     *       public void run() {
+     *           Assert.assertTrue(mA2dpService.stop());
+     *       }
+     *   });
+     * }
+     * </pre>
+     *
+     * @param looper the looper used to run the action
+     * @param action the action to run
+     */
+    public static void runOnLooperSync(Looper looper, Runnable action) {
+        if (Looper.myLooper() == looper) {
+            // requested thread is the same as the current thread. call directly.
+            action.run();
+        } else {
+            Handler handler = new Handler(looper);
+            SyncRunnable sr = new SyncRunnable(action);
+            handler.post(sr);
+            sr.waitForComplete();
+        }
+    }
+
+    /**
+     * Helper class used to run synchronously a runnable action on a looper.
+     */
+    private static final class SyncRunnable implements Runnable {
+        private final Runnable mTarget;
+        private volatile boolean mComplete = false;
+
+        SyncRunnable(Runnable target) {
+            mTarget = target;
+        }
+
+        @Override
+        public void run() {
+            mTarget.run();
+            synchronized (this) {
+                mComplete = true;
+                notifyAll();
+            }
+        }
+
+        public void waitForComplete() {
+            synchronized (this) {
+                while (!mComplete) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
new file mode 100644
index 0000000..bef3bdb
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -0,0 +1,879 @@
+/*
+ * 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.a2dp;
+
+import static org.mockito.Mockito.*;
+
+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.bluetooth.BluetoothUuid;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+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 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;
+
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeoutException;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class A2dpServiceTest {
+    private static final int MAX_CONNECTED_AUDIO_DEVICES = 5;
+
+    private BluetoothAdapter mAdapter;
+    private Context mTargetContext;
+    private A2dpService mA2dpService;
+    private BluetoothDevice mTestDevice;
+    private static final int TIMEOUT_MS = 1000;    // 1s
+
+    private BroadcastReceiver mA2dpIntentReceiver;
+    private final BlockingQueue<Intent> mConnectionStateChangedQueue = new LinkedBlockingQueue<>();
+    private final BlockingQueue<Intent> mAudioStateChangedQueue = new LinkedBlockingQueue<>();
+    private final BlockingQueue<Intent> mCodecConfigChangedQueue = new LinkedBlockingQueue<>();
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private A2dpNativeInterface mA2dpNativeInterface;
+
+    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when A2dpService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp));
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        TestUtils.setAdapterService(mAdapterService);
+        doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices();
+        doReturn(false).when(mAdapterService).isQuietModeEnabled();
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        startService();
+        mA2dpService.mA2dpNativeInterface = mA2dpNativeInterface;
+
+        // Override the timeout value to speed up the test
+        A2dpStateMachine.sConnectTimeoutMs = TIMEOUT_MS;    // 1s
+
+        // Set up the Connection State Changed receiver
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
+        filter.addAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
+        mA2dpIntentReceiver = new A2dpIntentReceiver();
+        mTargetContext.registerReceiver(mA2dpIntentReceiver, filter);
+
+        // 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)
+                .getRemoteUuids(any(BluetoothDevice.class));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp)) {
+            return;
+        }
+        stopService();
+        mTargetContext.unregisterReceiver(mA2dpIntentReceiver);
+        mConnectionStateChangedQueue.clear();
+        mAudioStateChangedQueue.clear();
+        mCodecConfigChangedQueue.clear();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    private void startService() throws TimeoutException {
+        TestUtils.startService(mServiceRule, A2dpService.class);
+        mA2dpService = A2dpService.getA2dpService();
+        Assert.assertNotNull(mA2dpService);
+    }
+
+    private void stopService() throws TimeoutException {
+        TestUtils.stopService(mServiceRule, A2dpService.class);
+        mA2dpService = A2dpService.getA2dpService();
+        Assert.assertNull(mA2dpService);
+    }
+
+    private class A2dpIntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
+                try {
+                    mConnectionStateChangedQueue.put(intent);
+                } catch (InterruptedException e) {
+                    Assert.fail("Cannot add Intent to the Connection State queue: "
+                                + e.getMessage());
+                }
+            }
+            if (BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED.equals(intent.getAction())) {
+                try {
+                    mAudioStateChangedQueue.put(intent);
+                } catch (InterruptedException e) {
+                    Assert.fail("Cannot add Intent to the Audio State queue: " + e.getMessage());
+                }
+            }
+            if (BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED.equals(intent.getAction())) {
+                try {
+                    mCodecConfigChangedQueue.put(intent);
+                } catch (InterruptedException e) {
+                    Assert.fail("Cannot add Intent to the Codec Config queue: " + e.getMessage());
+                }
+            }
+        }
+    }
+
+    private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device,
+                                             int newState, int prevState) {
+        Intent intent = TestUtils.waitForIntent(timeoutMs, mConnectionStateChangedQueue);
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
+                            intent.getAction());
+        Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                                                          -1));
+    }
+
+    private void verifyNoConnectionStateIntent(int timeoutMs) {
+        Intent intent = TestUtils.waitForNoIntent(timeoutMs, mConnectionStateChangedQueue);
+        Assert.assertNull(intent);
+    }
+
+    private void verifyAudioStateIntent(int timeoutMs, BluetoothDevice device,
+                                             int newState, int prevState) {
+        Intent intent = TestUtils.waitForIntent(timeoutMs, mAudioStateChangedQueue);
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED, intent.getAction());
+        Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                                                          -1));
+    }
+
+    private void verifyNoAudioStateIntent(int timeoutMs) {
+        Intent intent = TestUtils.waitForNoIntent(timeoutMs, mAudioStateChangedQueue);
+        Assert.assertNull(intent);
+    }
+
+    private void verifyCodecConfigIntent(int timeoutMs, BluetoothDevice device,
+                                         BluetoothCodecStatus codecStatus) {
+        Intent intent = TestUtils.waitForIntent(timeoutMs, mCodecConfigChangedQueue);
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED, intent.getAction());
+        Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(codecStatus,
+                            intent.getParcelableExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS));
+    }
+
+    private void verifyNoCodecConfigIntent(int timeoutMs) {
+        Intent intent = TestUtils.waitForNoIntent(timeoutMs, mCodecConfigChangedQueue);
+        Assert.assertNull(intent);
+    }
+
+    /**
+     * Test getting A2DP Service: getA2dpService()
+     */
+    @Test
+    public void testGetA2dpService() {
+        Assert.assertEquals(mA2dpService, A2dpService.getA2dpService());
+    }
+
+    /**
+     * Test stop A2DP Service
+     */
+    @Test
+    public void testStopA2dpService() {
+        // Prepare: connect and set active device
+        doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class));
+        connectDevice(mTestDevice);
+        Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice));
+        verify(mA2dpNativeInterface).setActiveDevice(mTestDevice);
+        // A2DP Service is already running: test stop(). Note: must be done on the main thread.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            public void run() {
+                Assert.assertTrue(mA2dpService.stop());
+            }
+        });
+        // Verify that setActiveDevice(null) was called during shutdown
+        verify(mA2dpNativeInterface).setActiveDevice(null);
+        // Try to restart the service. Note: must be done on the main thread.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            public void run() {
+                Assert.assertTrue(mA2dpService.start());
+            }
+        });
+    }
+
+    /**
+     * Test get/set priority for BluetoothDevice
+     */
+    @Test
+    public void testGetSetPriority() {
+        Assert.assertEquals("Initial device priority",
+                            BluetoothProfile.PRIORITY_UNDEFINED,
+                            mA2dpService.getPriority(mTestDevice));
+
+        Assert.assertTrue(mA2dpService.setPriority(mTestDevice,  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));
+        Assert.assertEquals("Setting device priority to PRIORITY_ON",
+                            BluetoothProfile.PRIORITY_ON,
+                            mA2dpService.getPriority(mTestDevice));
+
+        Assert.assertTrue(mA2dpService.setPriority(mTestDevice,
+                                                   BluetoothProfile.PRIORITY_AUTO_CONNECT));
+        Assert.assertEquals("Setting device priority to PRIORITY_AUTO_CONNECT",
+                            BluetoothProfile.PRIORITY_AUTO_CONNECT,
+                            mA2dpService.getPriority(mTestDevice));
+    }
+
+    /**
+     *  Test okToConnect method using various test cases
+     */
+    @Test
+    public void testOkToConnect() {
+        int badPriorityValue = 1024;
+        int badBondState = 42;
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, badPriorityValue, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, badPriorityValue, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, badPriorityValue, false);
+        testOkToConnectCase(mTestDevice,
+                badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToConnectCase(mTestDevice,
+                badBondState, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mTestDevice,
+                badBondState, BluetoothProfile.PRIORITY_ON, false);
+        testOkToConnectCase(mTestDevice,
+                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));
+    }
+
+
+    /**
+     * Test that an outgoing connection to device that does not have A2DP Sink UUID is rejected
+     */
+    @Test
+    public void testOutgoingConnectMissingAudioSinkUuid() {
+        // Update the device priority so okToConnect() returns true
+        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // Return AudioSource UUID instead of AudioSink
+        doReturn(new ParcelUuid[]{BluetoothUuid.AudioSource}).when(mAdapterService)
+                .getRemoteUuids(any(BluetoothDevice.class));
+
+        // Send a connect request
+        Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice));
+    }
+
+    /**
+     * Test that an outgoing connection to device with PRIORITY_OFF is rejected
+     */
+    @Test
+    public void testOutgoingConnectPriorityOff() {
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        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);
+
+        // Send a connect request
+        Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice));
+    }
+
+    /**
+     * Test that an outgoing connection times out
+     */
+    @Test
+    public void testOutgoingConnectTimeout() {
+        // Update the device priority so okToConnect() returns true
+        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mA2dpService.connect(mTestDevice));
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTING,
+                                    BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                            mA2dpService.getConnectionState(mTestDevice));
+
+        // Verify the connection state broadcast, and that we are in Disconnected state
+        verifyConnectionStateIntent(A2dpStateMachine.sConnectTimeoutMs * 2,
+                                    mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                                    BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+    }
+
+    /**
+     * Test that an outgoing connection/disconnection succeeds
+     */
+    @Test
+    public void testOutgoingConnectDisconnectSuccess() {
+        A2dpStackEvent connCompletedEvent;
+
+        // Update the device priority so okToConnect() returns true
+        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mA2dpService.connect(mTestDevice));
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTING,
+                                    BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                            mA2dpService.getConnectionState(mTestDevice));
+
+        // Send a message to trigger connection completed
+        connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mTestDevice;
+        connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
+        mA2dpService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTED,
+                                    BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+
+        // Verify the list of connected devices
+        Assert.assertTrue(mA2dpService.getConnectedDevices().contains(mTestDevice));
+
+        // Send a disconnect request
+        Assert.assertTrue("Disconnect failed", mA2dpService.disconnect(mTestDevice));
+
+        // Verify the connection state broadcast, and that we are in Disconnecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTING,
+                                    BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
+                            mA2dpService.getConnectionState(mTestDevice));
+
+        // Send a message to trigger disconnection completed
+        connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mTestDevice;
+        connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED;
+        mA2dpService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Disconnected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                                    BluetoothProfile.STATE_DISCONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+
+        // Verify the list of connected devices
+        Assert.assertFalse(mA2dpService.getConnectedDevices().contains(mTestDevice));
+    }
+
+    /**
+     * Test that an outgoing connection/disconnection succeeds
+     */
+    @Test
+    public void testMaxConnectDevices() {
+        A2dpStackEvent connCompletedEvent;
+        BluetoothDevice[] testDevices = new BluetoothDevice[MAX_CONNECTED_AUDIO_DEVICES];
+        BluetoothDevice extraTestDevice;
+
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // Prepare and connect all test devices
+        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);
+            // Send a connect request
+            Assert.assertTrue("Connect failed", mA2dpService.connect(testDevice));
+            // Verify the connection state broadcast, and that we are in Connecting state
+            verifyConnectionStateIntent(TIMEOUT_MS, testDevice, BluetoothProfile.STATE_CONNECTING,
+                                        BluetoothProfile.STATE_DISCONNECTED);
+            Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                                mA2dpService.getConnectionState(testDevice));
+            // Send a message to trigger connection completed
+            connCompletedEvent =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+            connCompletedEvent.device = testDevice;
+            connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
+            mA2dpService.messageFromNative(connCompletedEvent);
+
+            // Verify the connection state broadcast, and that we are in Connected state
+            verifyConnectionStateIntent(TIMEOUT_MS, testDevice, BluetoothProfile.STATE_CONNECTED,
+                                        BluetoothProfile.STATE_CONNECTING);
+            Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                                mA2dpService.getConnectionState(testDevice));
+            // Verify the list of connected devices
+            Assert.assertTrue(mA2dpService.getConnectedDevices().contains(testDevice));
+        }
+
+        // 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);
+        // Send a connect request
+        Assert.assertFalse("Connect expected to fail", mA2dpService.connect(extraTestDevice));
+    }
+
+    /**
+     * Test that only CONNECTION_STATE_CONNECTED or CONNECTION_STATE_CONNECTING A2DP stack events
+     * will create a state machine.
+     */
+    @Test
+    public void testCreateStateMachineStackEvents() {
+        A2dpStackEvent stackEvent;
+
+        // Update the device priority so okToConnect() returns true
+        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine should be created
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING,
+                                            BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                                            BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine should be created
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTED,
+                                            BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                                            BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created
+        generateUnexpectedConnectionMessageFromNative(mTestDevice,
+                                                      BluetoothProfile.STATE_DISCONNECTING,
+                                                      BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should not be created
+        generateUnexpectedConnectionMessageFromNative(mTestDevice,
+                                                      BluetoothProfile.STATE_DISCONNECTED,
+                                                      BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
+    }
+
+    /**
+     * Test that EVENT_TYPE_AUDIO_STATE_CHANGED and EVENT_TYPE_CODEC_CONFIG_CHANGED events
+     * are processed.
+     */
+    @Test
+    public void testProcessAudioStateChangedCodecConfigChangedEvents() {
+        A2dpStackEvent stackEvent;
+        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 codecConfig = codecConfigSbc;
+        BluetoothCodecConfig[] codecsLocalCapabilities = new BluetoothCodecConfig[1];
+        BluetoothCodecConfig[] codecsSelectableCapabilities = new BluetoothCodecConfig[1];
+        codecsLocalCapabilities[0] = codecConfigSbc;
+        codecsSelectableCapabilities[0] = codecConfigSbc;
+        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfig,
+                                                                    codecsLocalCapabilities,
+                                                                    codecsSelectableCapabilities);
+
+        // Update the device priority so okToConnect() returns true
+        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // A2DP stack event: EVENT_TYPE_AUDIO_STATE_CHANGED - state machine should not be created
+        generateUnexpectedAudioMessageFromNative(mTestDevice, A2dpStackEvent.AUDIO_STATE_STARTED,
+                                                 BluetoothA2dp.STATE_PLAYING,
+                                                 BluetoothA2dp.STATE_NOT_PLAYING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: EVENT_TYPE_CODEC_CONFIG_CHANGED - state machine should not be created
+        generateUnexpectedCodecMessageFromNative(mTestDevice, codecStatus);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine should be created
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTED,
+                                            BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: EVENT_TYPE_AUDIO_STATE_CHANGED - Intent broadcast should be generated
+        // NOTE: The first message (STATE_PLAYING -> STATE_NOT_PLAYING) is generated internally
+        // by the state machine when Connected, and needs to be extracted first before generating
+        // the actual message from native.
+        verifyAudioStateIntent(TIMEOUT_MS, mTestDevice, BluetoothA2dp.STATE_NOT_PLAYING,
+                               BluetoothA2dp.STATE_PLAYING);
+        generateAudioMessageFromNative(mTestDevice,
+                                       A2dpStackEvent.AUDIO_STATE_STARTED,
+                                       BluetoothA2dp.STATE_PLAYING,
+                                       BluetoothA2dp.STATE_NOT_PLAYING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: EVENT_TYPE_CODEC_CONFIG_CHANGED - Intent broadcast should be generated
+        generateCodecMessageFromNative(mTestDevice, codecStatus);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                                            BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
+    }
+
+    /**
+     * Test that a state machine in DISCONNECTED state is removed only after the device is unbond.
+     */
+    @Test
+    public void testDeleteStateMachineUnbondEvents() {
+        A2dpStackEvent stackEvent;
+
+        // Update the device priority so okToConnect() returns true
+        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine should be created
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING,
+                                            BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+        // Device unbond - state machine is not removed
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine is not removed
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_BONDED);
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTED,
+                                            BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+        // Device unbond - state machine is not removed
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_DISCONNECTING - state machine is not removed
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_BONDED);
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTING,
+                                            BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+        // Device unbond - state machine is not removed
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_BONDED);
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                                            BluetoothProfile.STATE_DISCONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+        // Device unbond - state machine is removed
+        mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
+    }
+
+    /**
+     * Test that a CONNECTION_STATE_DISCONNECTED A2DP stack event will remove the state machine
+     * only if the device is unbond.
+     */
+    @Test
+    public void testDeleteStateMachineDisconnectEvents() {
+        A2dpStackEvent stackEvent;
+
+        // Update the device priority so okToConnect() returns true
+        mA2dpService.setPriority(mTestDevice, BluetoothProfile.PRIORITY_ON);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine should be created
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING,
+                                            BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                                            BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine remains
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING,
+                                            BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // Device bond state marked as unbond - state machine is not removed
+        doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService)
+                .getBondState(any(BluetoothDevice.class));
+        Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice));
+
+        // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is removed
+        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                                            BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                            mA2dpService.getConnectionState(mTestDevice));
+        Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice));
+    }
+
+    private void connectDevice(BluetoothDevice device) {
+        A2dpStackEvent connCompletedEvent;
+
+        List<BluetoothDevice> prevConnectedDevices = mA2dpService.getConnectedDevices();
+
+        // Update the device priority so okToConnect() returns true
+        mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(device);
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(device);
+
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mA2dpService.connect(device));
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTING,
+                                    BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                            mA2dpService.getConnectionState(device));
+
+        // Send a message to trigger connection completed
+        connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = device;
+        connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
+        mA2dpService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTED,
+                                    BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                            mA2dpService.getConnectionState(device));
+
+        // Verify that the device is in the list of connected devices
+        Assert.assertTrue(mA2dpService.getConnectedDevices().contains(device));
+        // Verify the list of previously connected devices
+        for (BluetoothDevice prevDevice : prevConnectedDevices) {
+            Assert.assertTrue(mA2dpService.getConnectedDevices().contains(prevDevice));
+        }
+    }
+
+    private void generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState,
+                                                     int oldConnectionState) {
+        A2dpStackEvent stackEvent =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        stackEvent.device = device;
+        stackEvent.valueInt = newConnectionState;
+        mA2dpService.messageFromNative(stackEvent);
+        // Verify the connection state broadcast
+        verifyConnectionStateIntent(TIMEOUT_MS, device, newConnectionState, oldConnectionState);
+    }
+
+    private void generateUnexpectedConnectionMessageFromNative(BluetoothDevice device,
+                                                               int newConnectionState,
+                                                               int oldConnectionState) {
+        A2dpStackEvent stackEvent =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        stackEvent.device = device;
+        stackEvent.valueInt = newConnectionState;
+        mA2dpService.messageFromNative(stackEvent);
+        // Verify the connection state broadcast
+        verifyNoConnectionStateIntent(TIMEOUT_MS);
+    }
+
+    private void generateAudioMessageFromNative(BluetoothDevice device, int audioStackEvent,
+                                                int newAudioState, int oldAudioState) {
+        A2dpStackEvent stackEvent =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+        stackEvent.device = device;
+        stackEvent.valueInt = audioStackEvent;
+        mA2dpService.messageFromNative(stackEvent);
+        // Verify the audio state broadcast
+        verifyAudioStateIntent(TIMEOUT_MS, device, newAudioState, oldAudioState);
+    }
+
+    private void generateUnexpectedAudioMessageFromNative(BluetoothDevice device,
+                                                          int audioStackEvent, int newAudioState,
+                                                          int oldAudioState) {
+        A2dpStackEvent stackEvent =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+        stackEvent.device = device;
+        stackEvent.valueInt = audioStackEvent;
+        mA2dpService.messageFromNative(stackEvent);
+        // Verify the audio state broadcast
+        verifyNoAudioStateIntent(TIMEOUT_MS);
+    }
+
+    private void generateCodecMessageFromNative(BluetoothDevice device,
+                                                BluetoothCodecStatus codecStatus) {
+        A2dpStackEvent stackEvent =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED);
+        stackEvent.device = device;
+        stackEvent.codecStatus = codecStatus;
+        mA2dpService.messageFromNative(stackEvent);
+        // Verify the codec status broadcast
+        verifyCodecConfigIntent(TIMEOUT_MS, device, codecStatus);
+    }
+
+    private void generateUnexpectedCodecMessageFromNative(BluetoothDevice device,
+                                                          BluetoothCodecStatus codecStatus) {
+        A2dpStackEvent stackEvent =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED);
+        stackEvent.device = device;
+        stackEvent.codecStatus = codecStatus;
+        mA2dpService.messageFromNative(stackEvent);
+        // Verify the codec status broadcast
+        verifyNoCodecConfigIntent(TIMEOUT_MS);
+    }
+
+    /**
+     * Helper function to test okToConnect() method.
+     *
+     * @param device test device
+     * @param bondState bond state value, could be invalid
+     * @param priority value, could be invalid, coudl be invalid
+     * @param expected expected result from okToConnect()
+     */
+    private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
+            boolean expected) {
+        doReturn(bondState).when(mAdapterService).getBondState(device);
+        Assert.assertTrue(mA2dpService.setPriority(device, priority));
+
+        // Test when the AdapterService is in non-quiet mode: the result should not depend
+        // on whether the connection request is outgoing or incoming.
+        doReturn(false).when(mAdapterService).isQuietModeEnabled();
+        Assert.assertEquals(expected, mA2dpService.okToConnect(device, true));  // Outgoing
+        Assert.assertEquals(expected, mA2dpService.okToConnect(device, false)); // Incoming
+
+        // Test when the AdapterService is in quiet mode: the result should always be
+        // false when the connection request is incoming.
+        doReturn(true).when(mAdapterService).isQuietModeEnabled();
+        Assert.assertEquals(expected, mA2dpService.okToConnect(device, true));  // Outgoing
+        Assert.assertEquals(false, mA2dpService.okToConnect(device, false)); // Incoming
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
new file mode 100644
index 0000000..0b825bf
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2017 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.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+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 com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+
+import org.hamcrest.core.IsInstanceOf;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+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 A2dpStateMachineTest {
+    private BluetoothAdapter mAdapter;
+    private Context mTargetContext;
+    private HandlerThread mHandlerThread;
+    private A2dpStateMachine mA2dpStateMachine;
+    private BluetoothDevice mTestDevice;
+    private static final int TIMEOUT_MS = 1000;    // 1s
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private A2dpService mA2dpService;
+    @Mock private A2dpNativeInterface mA2dpNativeInterface;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when A2dpService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp));
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        // Get a device for testing
+        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+
+        // Set up thread and looper
+        mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread");
+        mHandlerThread.start();
+        mA2dpStateMachine = new A2dpStateMachine(mTestDevice, mA2dpService,
+                                                 mA2dpNativeInterface, mHandlerThread.getLooper());
+        // Override the timeout value to speed up the test
+        A2dpStateMachine.sConnectTimeoutMs = 1000;     // 1s
+        mA2dpStateMachine.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp)) {
+            return;
+        }
+        mA2dpStateMachine.doQuit();
+        mHandlerThread.quit();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    /**
+     * Test that default state is disconnected
+     */
+    @Test
+    public void testDefaultDisconnectedState() {
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mA2dpStateMachine.getConnectionState());
+    }
+
+    /**
+     * Allow/disallow connection to any device.
+     *
+     * @param allow if true, connection is allowed
+     */
+    private void allowConnection(boolean allow) {
+        doReturn(allow).when(mA2dpService).okToConnect(any(BluetoothDevice.class),
+                                                       anyBoolean());
+    }
+
+    /**
+     * Test that an incoming connection with low priority is rejected
+     */
+    @Test
+    public void testIncomingPriorityReject() {
+        allowConnection(false);
+
+        // Inject an event for when incoming connection is requested
+        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 no connection state broadcast is executed
+        verify(mA2dpService, after(TIMEOUT_MS).never()).sendBroadcast(any(Intent.class),
+                                                                      anyString());
+        // Check that we are in Disconnected state
+        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
+                          IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test that an incoming connection with high priority is accepted
+     */
+    @Test
+    public void testIncomingPriorityAccept() {
+        allowConnection(true);
+
+        // Inject an event for when incoming connection is requested
+        A2dpStackEvent connStCh =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTING;
+        mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(intentArgument1.capture(),
+                                                                         anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Connecting state
+        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
+                          IsInstanceOf.instanceOf(A2dpStateMachine.Connecting.class));
+
+        // Send a message to trigger connection completed
+        A2dpStackEvent connCompletedEvent =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mTestDevice;
+        connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
+        mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connCompletedEvent);
+
+        // 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(3)).sendBroadcast(intentArgument2.capture(),
+                anyString());
+        // Verify that the last broadcast was to change the A2DP playing state
+        // to STATE_NOT_PLAYING
+        Assert.assertEquals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED,
+                intentArgument2.getValue().getAction());
+        Assert.assertEquals(BluetoothA2dp.STATE_NOT_PLAYING,
+                intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        // Check that we are in Connected state
+        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
+                          IsInstanceOf.instanceOf(A2dpStateMachine.Connected.class));
+    }
+
+    /**
+     * Test that an outgoing connection times out
+     */
+    @Test
+    public void testOutgoingTimeout() {
+        allowConnection(true);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // Send a connect request
+        mA2dpStateMachine.sendMessage(A2dpStateMachine.CONNECT, mTestDevice);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(intentArgument1.capture(),
+                                                                         anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Connecting state
+        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(A2dpStateMachine.Connecting.class));
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mA2dpService, timeout(A2dpStateMachine.sConnectTimeoutMs * 2).times(
+                2)).sendBroadcast(intentArgument2.capture(), anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Disconnected state
+        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
+                          IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test that an incoming connection times out
+     */
+    @Test
+    public void testIncomingTimeout() {
+        allowConnection(true);
+        doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
+        doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
+
+        // Inject an event for when incoming connection is requested
+        A2dpStackEvent connStCh =
+                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTING;
+        mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(intentArgument1.capture(),
+                anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Connecting state
+        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(A2dpStateMachine.Connecting.class));
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mA2dpService, timeout(A2dpStateMachine.sConnectTimeoutMs * 2).times(
+                2)).sendBroadcast(intentArgument2.capture(), anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Disconnected state
+        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
new file mode 100644
index 0000000..2735c11
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.a2dpsink;
+
+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 A2dpSinkServiceTest {
+    private A2dpSinkService 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 A2dpSinkService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp_sink));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, A2dpSinkService.class);
+        mService = A2dpSinkService.getA2dpSinkService();
+        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_a2dp_sink)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, A2dpSinkService.class);
+        mService = A2dpSinkService.getA2dpSinkService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(A2dpSinkService.getA2dpSinkService());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
new file mode 100644
index 0000000..eddab48
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 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 static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+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 com.android.bluetooth.R;
+
+import org.junit.Assume;
+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 A2dpSinkStreamHandlerTest {
+    private static final int DUCK_PERCENT = 75;
+    private HandlerThread mHandlerThread;
+    private A2dpSinkStreamHandler mStreamHandler;
+    private Context mTargetContext;
+
+    @Mock private Context mMockContext;
+
+    @Mock private A2dpSinkStateMachine mMockA2dpSink;
+
+    @Mock private AudioManager mMockAudioManager;
+
+    @Mock private Resources mMockResources;
+
+    @Mock private PackageManager mMockPackageManager;
+
+    @Before
+    public void setUp() {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when A2dpSinkService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp_sink));
+        MockitoAnnotations.initMocks(this);
+        // Mock the looper
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mHandlerThread = new HandlerThread("A2dpSinkStreamHandlerTest");
+        mHandlerThread.start();
+
+        when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mMockAudioManager);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getInteger(anyInt())).thenReturn(DUCK_PERCENT);
+        when(mMockAudioManager.requestAudioFocus(any())).thenReturn(
+                AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+        when(mMockAudioManager.abandonAudioFocus(any())).thenReturn(AudioManager.AUDIOFOCUS_GAIN);
+        doNothing().when(mMockA2dpSink).informAudioTrackGainNative(anyFloat());
+        when(mMockContext.getMainLooper()).thenReturn(mHandlerThread.getLooper());
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockPackageManager.hasSystemFeature(any())).thenReturn(false);
+
+        mStreamHandler = spy(new A2dpSinkStreamHandler(mMockA2dpSink, mMockContext));
+    }
+
+    @Test
+    public void testSrcStart() {
+        // Stream started without local play, expect no change in streaming.
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START));
+        verify(mMockAudioManager, times(0)).requestAudioFocus(any());
+        verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
+        verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
+    }
+
+    @Test
+    public void testSrcStop() {
+        // Stream stopped without local play, expect no change in streaming.
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP));
+        verify(mMockAudioManager, times(0)).requestAudioFocus(any());
+        verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
+        verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
+    }
+
+    @Test
+    public void testSnkPlay() {
+        // Play was pressed locally, expect streaming to start.
+        mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SNK_PLAY));
+        verify(mMockAudioManager, times(1)).requestAudioFocus(any());
+        verify(mMockA2dpSink, times(1)).informAudioFocusStateNative(1);
+        verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(1.0f);
+    }
+
+    @Test
+    public void testSnkPause() {
+        // Pause was pressed locally, expect streaming to stop.
+        mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SNK_PAUSE));
+        verify(mMockAudioManager, times(0)).requestAudioFocus(any());
+        verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
+        verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
+    }
+
+    @Test
+    public void testDisconnect() {
+        // Remote device was disconnected, expect streaming to stop.
+        testSnkPlay();
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.DISCONNECT));
+        verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
+        verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
+    }
+
+    @Test
+    public void testSrcPlay() {
+        // Play was pressed remotely, expect no streaming due to lack of audio focus.
+        mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY));
+        verify(mMockAudioManager, times(0)).requestAudioFocus(any());
+        verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
+        verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
+    }
+
+    @Test
+    public void testSrcPlayIot() {
+        // Play was pressed remotely for an iot device, expect streaming to start.
+        when(mMockPackageManager.hasSystemFeature(any())).thenReturn(true);
+        mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY));
+        verify(mMockAudioManager, times(1)).requestAudioFocus(any());
+        verify(mMockA2dpSink, times(1)).informAudioFocusStateNative(1);
+        verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(1.0f);
+    }
+
+    @Test
+    public void testSrcPause() {
+        // Play was pressed locally, expect streaming to start.
+        mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY));
+        verify(mMockAudioManager, times(0)).requestAudioFocus(any());
+        verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(1);
+        verify(mMockA2dpSink, times(0)).informAudioTrackGainNative(1.0f);
+    }
+
+    @Test
+    public void testFocusGain() {
+        // Focus was gained, expect streaming to resume.
+        testSnkPlay();
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
+                        AudioManager.AUDIOFOCUS_GAIN));
+        verify(mMockAudioManager, times(1)).requestAudioFocus(any());
+        verify(mMockA2dpSink, times(2)).informAudioFocusStateNative(1);
+        verify(mMockA2dpSink, times(2)).informAudioTrackGainNative(1.0f);
+    }
+
+    @Test
+    public void testFocusTransientMayDuck() {
+        // TransientMayDuck focus was gained, expect audio stream to duck.
+        testSnkPlay();
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
+                        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK));
+        verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(DUCK_PERCENT / 100.0f);
+    }
+
+    @Test
+    public void testFocusLostTransient() {
+        // Focus was lost transiently, expect streaming to stop.
+        testSnkPlay();
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
+                        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
+        verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
+        verify(mMockA2dpSink, times(1)).informAudioFocusStateNative(0);
+    }
+
+    @Test
+    public void testFocusLost() {
+        // Focus was lost permanently, expect streaming to stop.
+        testSnkPlay();
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
+                        AudioManager.AUDIOFOCUS_LOSS));
+        verify(mMockAudioManager, times(1)).abandonAudioFocus(any());
+        verify(mMockA2dpSink, times(1)).informAudioFocusStateNative(0);
+    }
+}
diff --git a/tests/src/com/android/bluetooth/avrcp/AvrcpTest.java b/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java
similarity index 67%
rename from tests/src/com/android/bluetooth/avrcp/AvrcpTest.java
rename to tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java
index ab4fd9a..5af45e1 100644
--- a/tests/src/com/android/bluetooth/avrcp/AvrcpTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcp/AvrcpTest.java
@@ -1,42 +1,46 @@
 package com.android.bluetooth.avrcp;
 
-import android.bluetooth.BluetoothAvrcp;
+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.session.MediaSession;
-import android.media.session.MediaSession.QueueItem;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
 import android.media.AudioManager;
-import android.media.session.MediaSessionManager;
-import android.os.Bundle;
 import android.os.Looper;
-import android.test.AndroidTestCase;
-import android.util.Log;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
 
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Arrays;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.ArrayList;
+import java.util.List;
 
-import static org.mockito.Mockito.isA;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
-public class AvrcpTest extends AndroidTestCase {
-
+/**
+ * Unit tests for {@link Avrcp}
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AvrcpTest {
+    @Test
     public void testCanStart() {
-        if (Looper.myLooper() == null) Looper.prepare();
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
 
-        Avrcp a = Avrcp.make(getContext());
+        Avrcp a = Avrcp.make(InstrumentationRegistry.getTargetContext());
     }
 
+    @Test
     public void testFailedBrowseStart() {
-        if (Looper.myLooper() == null) Looper.prepare();
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
 
         Context mockContext = mock(Context.class);
         AudioManager mockAudioManager = mock(AudioManager.class);
@@ -62,7 +66,8 @@
         fakePackage.serviceInfo = fakeService;
         fakePackage.nonLocalizedLabel = "Fake Package";
         resInfos.add(fakePackage);
-        when(mockPackageManager.queryIntentServices(isA(Intent.class), anyInt())).thenReturn(resInfos);
+        when(mockPackageManager.queryIntentServices(isA(Intent.class), anyInt())).thenReturn(
+                resInfos);
 
         when(mockContext.startService(isA(Intent.class))).thenThrow(new SecurityException("test"));
 
@@ -71,7 +76,8 @@
         try {
             Avrcp a = Avrcp.make(mockContext);
         } catch (SecurityException e) {
-            fail("Threw SecurityException instead of protecting against it: " + e.toString());
+            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
new file mode 100644
index 0000000..ff44f06
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/avrcp/EvictingQueueTest.java
@@ -0,0 +1,58 @@
+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
new file mode 100644
index 0000000..8bf4679
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.avrcpcontroller;
+
+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 AvrcpControllerServiceTest {
+    private AvrcpControllerService 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 AvrcpControllerService is not enabled",
+                mTargetContext.getResources()
+                        .getBoolean(R.bool.profile_supported_avrcp_controller));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, AvrcpControllerService.class);
+        mService = AvrcpControllerService.getAvrcpControllerService();
+        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_avrcp_controller)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, AvrcpControllerService.class);
+        mService = AvrcpControllerService.getAvrcpControllerService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(AvrcpControllerService.getAvrcpControllerService());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
new file mode 100644
index 0000000..2d01bef
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
@@ -0,0 +1,374 @@
+/*
+ * 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.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.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+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 com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.hearingaid.HearingAidService;
+import com.android.bluetooth.hfp.HeadsetService;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+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 ActiveDeviceManagerTest {
+    private BluetoothAdapter mAdapter;
+    private Context mContext;
+    private BluetoothDevice mA2dpDevice;
+    private BluetoothDevice mHeadsetDevice;
+    private BluetoothDevice mA2dpHeadsetDevice;
+    private BluetoothDevice mHearingAidDevice;
+    private ActiveDeviceManager mActiveDeviceManager;
+    private static final int TIMEOUT_MS = 1000;
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private ServiceFactory mServiceFactory;
+    @Mock private A2dpService mA2dpService;
+    @Mock private HeadsetService mHeadsetService;
+    @Mock private HearingAidService mHearingAidService;
+    @Mock private AudioManager mAudioManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when A2dpService is not enabled",
+                mContext.getResources().getBoolean(R.bool.profile_supported_a2dp));
+        Assume.assumeTrue("Ignore test when HeadsetService is not enabled",
+                mContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp));
+
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mServiceFactory.getA2dpService()).thenReturn(mA2dpService);
+        when(mServiceFactory.getHeadsetService()).thenReturn(mHeadsetService);
+        when(mServiceFactory.getHearingAidService()).thenReturn(mHearingAidService);
+        when(mA2dpService.setActiveDevice(any())).thenReturn(true);
+        when(mHeadsetService.setActiveDevice(any())).thenReturn(true);
+        when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
+
+        mActiveDeviceManager = new ActiveDeviceManager(mAdapterService, mServiceFactory);
+        mActiveDeviceManager.start();
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        // Get devices for testing
+        mA2dpDevice = TestUtils.getTestDevice(mAdapter, 0);
+        mHeadsetDevice = TestUtils.getTestDevice(mAdapter, 1);
+        mA2dpHeadsetDevice = TestUtils.getTestDevice(mAdapter, 2);
+        mHearingAidDevice = TestUtils.getTestDevice(mAdapter, 3);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp)
+                || !mContext.getResources().getBoolean(R.bool.profile_supported_a2dp)) {
+            return;
+        }
+        mActiveDeviceManager.cleanup();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testSetUpAndTearDown() {}
+
+    /**
+     * One A2DP is connected.
+     */
+    @Test
+    public void onlyA2dpConnected_setA2dpActive() {
+        a2dpConnected(mA2dpDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
+    }
+
+    /**
+     * Two A2DP are connected. Should set the second one active.
+     */
+    @Test
+    public void secondA2dpConnected_setSecondA2dpActive() {
+        a2dpConnected(mA2dpDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
+
+        a2dpConnected(mA2dpHeadsetDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
+    }
+
+    /**
+     * One A2DP is connected and disconnected later. Should then set active device to null.
+     */
+    @Test
+    public void lastA2dpDisconnected_clearA2dpActive() {
+        a2dpConnected(mA2dpDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
+
+        a2dpDisconnected(mA2dpDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+    }
+
+    /**
+     * Two A2DP are connected and active device is explicitly set.
+     */
+    @Test
+    public void a2dpActiveDeviceSelected_setActive() {
+        a2dpConnected(mA2dpDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
+
+        a2dpConnected(mA2dpHeadsetDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
+
+        a2dpActiveDeviceChanged(mA2dpDevice);
+        // Don't call mA2dpService.setActiveDevice()
+        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+        verify(mA2dpService, times(1)).setActiveDevice(mA2dpDevice);
+        Assert.assertEquals(mA2dpDevice, mActiveDeviceManager.getA2dpActiveDevice());
+    }
+
+    /**
+     * One Headset is connected.
+     */
+    @Test
+    public void onlyHeadsetConnected_setHeadsetActive() {
+        headsetConnected(mHeadsetDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
+    }
+
+    /**
+     * Two Headset are connected. Should set the second one active.
+     */
+    @Test
+    public void secondHeadsetConnected_setSecondHeadsetActive() {
+        headsetConnected(mHeadsetDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
+
+        headsetConnected(mA2dpHeadsetDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
+    }
+
+    /**
+     * One Headset is connected and disconnected later. Should then set active device to null.
+     */
+    @Test
+    public void lastHeadsetDisconnected_clearHeadsetActive() {
+        headsetConnected(mHeadsetDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
+
+        headsetDisconnected(mHeadsetDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+    }
+
+    /**
+     * Two Headset are connected and active device is explicitly set.
+     */
+    @Test
+    public void headsetActiveDeviceSelected_setActive() {
+        headsetConnected(mHeadsetDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
+
+        headsetConnected(mA2dpHeadsetDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
+
+        headsetActiveDeviceChanged(mHeadsetDevice);
+        // Don't call mHeadsetService.setActiveDevice()
+        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+        verify(mHeadsetService, times(1)).setActiveDevice(mHeadsetDevice);
+        Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice());
+    }
+
+    /**
+     * A combo (A2DP + Headset) device is connected. Then a Hearing Aid is connected.
+     */
+    @Test
+    public void hearingAidActive_clearA2dpAndHeadsetActive() {
+        Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
+                mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+
+        a2dpConnected(mA2dpHeadsetDevice);
+        headsetConnected(mA2dpHeadsetDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
+
+        hearingAidActiveDeviceChanged(mHearingAidDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+    }
+
+    /**
+     * A Hearing Aid is connected. Then a combo (A2DP + Headset) device is connected.
+     */
+    @Test
+    public void hearingAidActive_dontSetA2dpAndHeadsetActive() {
+        Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
+                mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+
+        hearingAidActiveDeviceChanged(mHearingAidDevice);
+        a2dpConnected(mA2dpHeadsetDevice);
+        headsetConnected(mA2dpHeadsetDevice);
+
+        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+        verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice);
+        verify(mHeadsetService, never()).setActiveDevice(mA2dpHeadsetDevice);
+    }
+
+    /**
+     * A Hearing Aid is connected. Then an A2DP active device is explicitly set.
+     */
+    @Test
+    public void hearingAidActive_setA2dpActiveExplicitly() {
+        Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
+                mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+
+        hearingAidActiveDeviceChanged(mHearingAidDevice);
+        a2dpConnected(mA2dpHeadsetDevice);
+        a2dpActiveDeviceChanged(mA2dpHeadsetDevice);
+
+        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+        verify(mHearingAidService).setActiveDevice(isNull());
+        // Don't call mA2dpService.setActiveDevice()
+        verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice);
+        Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getA2dpActiveDevice());
+        Assert.assertEquals(null, mActiveDeviceManager.getHearingAidActiveDevice());
+    }
+
+    /**
+     * A Hearing Aid is connected. Then a Headset active device is explicitly set.
+     */
+    @Test
+    public void hearingAidActive_setHeadsetActiveExplicitly() {
+        Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
+                mContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid));
+
+        hearingAidActiveDeviceChanged(mHearingAidDevice);
+        headsetConnected(mA2dpHeadsetDevice);
+        headsetActiveDeviceChanged(mA2dpHeadsetDevice);
+
+        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+        verify(mHearingAidService).setActiveDevice(isNull());
+        // Don't call mHeadsetService.setActiveDevice()
+        verify(mHeadsetService, never()).setActiveDevice(mA2dpHeadsetDevice);
+        Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice());
+        Assert.assertEquals(null, mActiveDeviceManager.getHearingAidActiveDevice());
+    }
+
+    /**
+     * A wired audio device is connected. Then all active devices are set to null.
+     */
+    @Test
+    public void wiredAudioDeviceConnected_setAllActiveDevicesNull() {
+        a2dpConnected(mA2dpDevice);
+        headsetConnected(mHeadsetDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
+
+        mActiveDeviceManager.wiredAudioDeviceConnected();
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+        verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+    }
+
+    /**
+     * 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);
+        mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+    }
+
+    /**
+     * 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);
+        mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+    }
+
+    /**
+     * Helper to indicate A2dp active device changed for a device.
+     */
+    private void a2dpActiveDeviceChanged(BluetoothDevice device) {
+        Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+    }
+
+    /**
+     * 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);
+        mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+    }
+
+    /**
+     * 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);
+        mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+    }
+
+    /**
+     * Helper to indicate Headset active device changed for a device.
+     */
+    private void headsetActiveDeviceChanged(BluetoothDevice device) {
+        Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+    }
+
+    /**
+     * Helper to indicate Hearing Aid active device changed for a device.
+     */
+    private void hearingAidActiveDeviceChanged(BluetoothDevice device) {
+        Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
new file mode 100644
index 0000000..2b19a20
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright 2017 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.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 android.app.AlarmManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.IBluetoothCallback;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.Looper;
+import android.os.PowerManager;
+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 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 AdapterServiceTest {
+    private AdapterService mAdapterService;
+
+    private @Mock Context mMockContext;
+    private @Mock ApplicationInfo mMockApplicationInfo;
+    private @Mock AlarmManager mMockAlarmManager;
+    private @Mock Resources mMockResources;
+    private @Mock UserManager mMockUserManager;
+    private @Mock ProfileService mMockGattService;
+    private @Mock ProfileService mMockService;
+    private @Mock ProfileService mMockService2;
+    private @Mock IBluetoothCallback mIBluetoothCallback;
+    private @Mock Binder mBinder;
+    private @Mock AudioManager mAudioManager;
+
+    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 PowerManager mPowerManager;
+    private PackageManager mMockPackageManager;
+    private MockContentResolver mMockContentResolver;
+
+    @Before
+    public void setUp() throws PackageManager.NameNotFoundException {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mAdapterService = new AdapterService();
+            }
+        });
+        mMockPackageManager = mock(PackageManager.class);
+        mMockContentResolver = new MockContentResolver(mMockContext);
+        MockitoAnnotations.initMocks(this);
+        mPowerManager = (PowerManager) InstrumentationRegistry.getTargetContext()
+                .getSystemService(Context.POWER_SERVICE);
+
+        when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo);
+        when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+        when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockContext.getUserId()).thenReturn(Process.BLUETOOTH_UID);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+        when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+        when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager);
+        when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+
+        when(mMockResources.getBoolean(R.bool.profile_supported_gatt)).thenReturn(true);
+        when(mMockResources.getBoolean(R.bool.profile_supported_pbap)).thenReturn(true);
+        when(mMockResources.getBoolean(R.bool.profile_supported_pan)).thenReturn(true);
+
+        when(mIBluetoothCallback.asBinder()).thenReturn(mBinder);
+
+        doReturn(Process.BLUETOOTH_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(any(), anyInt(), anyInt());
+
+        when(mMockGattService.getName()).thenReturn("GattService");
+        when(mMockService.getName()).thenReturn("Service1");
+        when(mMockService2.getName()).thenReturn("Service2");
+
+        // Attach a context to the service for permission checks.
+        mAdapterService.attach(mMockContext, null, null, null, null, null);
+
+        mAdapterService.onCreate();
+        mAdapterService.registerCallback(mIBluetoothCallback);
+
+        Config.init(mMockContext);
+    }
+
+    @After
+    public void tearDown() {
+        mAdapterService.unregisterCallback(mIBluetoothCallback);
+        mAdapterService.cleanup();
+        Config.init(InstrumentationRegistry.getTargetContext());
+    }
+
+    private void verifyStateChange(int prevState, int currState, int callNumber, int timeoutMs) {
+        try {
+            verify(mIBluetoothCallback, timeout(timeoutMs)
+                    .times(callNumber)).onBluetoothStateChange(prevState, currState);
+        } catch (Exception e) {
+            // the mocked onBluetoothStateChange doesn't throw exceptions
+        }
+    }
+
+    private void doEnable(int invocationNumber, boolean onlyGatt) {
+        Assert.assertFalse(mAdapterService.isEnabled());
+
+        final int startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2
+
+        mAdapterService.enable();
+
+        verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON,
+                invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+        // Start GATT
+        verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(
+                startServiceCalls * invocationNumber + 1)).startService(any());
+        mAdapterService.addProfile(mMockGattService);
+        mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_ON);
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON,
+                invocationNumber + 1, NATIVE_INIT_MS);
+
+        mAdapterService.onLeServiceUp();
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON,
+                invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+        if (!onlyGatt) {
+            // Start Mock PBAP and PAN services
+            verify(mMockContext, timeout(ONE_SECOND_MS).times(
+                    startServiceCalls * invocationNumber + 3)).startService(any());
+            mAdapterService.addProfile(mMockService);
+            mAdapterService.addProfile(mMockService2);
+            mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_ON);
+            mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_ON);
+        }
+
+        verifyStateChange(BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_ON,
+                invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+        final int scanMode = mAdapterService.getScanMode();
+        Assert.assertTrue(scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
+                || scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+        Assert.assertTrue(mAdapterService.isEnabled());
+    }
+
+    private void doDisable(int invocationNumber, boolean onlyGatt) {
+        Assert.assertTrue(mAdapterService.isEnabled());
+
+        final int startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2
+
+        mAdapterService.disable();
+
+        verifyStateChange(BluetoothAdapter.STATE_ON, BluetoothAdapter.STATE_TURNING_OFF,
+                invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+        if (!onlyGatt) {
+            // Stop PBAP and PAN
+            verify(mMockContext, timeout(ONE_SECOND_MS).times(
+                    startServiceCalls * invocationNumber + 5)).startService(any());
+            mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF);
+            mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_OFF);
+        }
+
+        verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON,
+                invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+        mAdapterService.onBrEdrDown();
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF,
+                invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+        // Stop GATT
+        verify(mMockContext, timeout(ONE_SECOND_MS).times(
+                startServiceCalls * invocationNumber + startServiceCalls)).startService(any());
+        mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF);
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF,
+                invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+        Assert.assertFalse(mAdapterService.isEnabled());
+    }
+
+    /**
+     * Test: Turn Bluetooth on.
+     * Check whether the AdapterService gets started.
+     */
+    @Test
+    public void testEnable() {
+        doEnable(0, false);
+    }
+
+    /**
+     * Test: Turn Bluetooth on/off.
+     * Check whether the AdapterService gets started and stopped.
+     */
+    @Test
+    public void testEnableDisable() {
+        doEnable(0, false);
+        doDisable(0, false);
+    }
+
+    /**
+     * Test: Turn Bluetooth on/off with only GATT supported.
+     * Check whether the AdapterService gets started and stopped.
+     */
+    @Test
+    public void testEnableDisableOnlyGatt() {
+        Context mockContext = mock(Context.class);
+        Resources mockResources = mock(Resources.class);
+
+        when(mockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo);
+        when(mockContext.getContentResolver()).thenReturn(mMockContentResolver);
+        when(mockContext.getApplicationContext()).thenReturn(mockContext);
+        when(mockContext.getResources()).thenReturn(mockResources);
+        when(mockContext.getUserId()).thenReturn(Process.BLUETOOTH_UID);
+        when(mockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+        when(mockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+        when(mockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager);
+
+        when(mockResources.getBoolean(R.bool.profile_supported_gatt)).thenReturn(true);
+
+        Config.init(mockContext);
+        doEnable(0, true);
+        doDisable(0, true);
+    }
+
+    /**
+     * Test: Don't start GATT
+     * Check whether the AdapterService quits gracefully
+     */
+    @Test
+    public void testGattStartTimeout() {
+        Assert.assertFalse(mAdapterService.isEnabled());
+
+        mAdapterService.enable();
+
+        verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON, 1,
+                CONTEXT_SWITCH_MS);
+
+        // Start GATT
+        verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(1)).startService(any());
+        mAdapterService.addProfile(mMockGattService);
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON,
+                BluetoothAdapter.STATE_BLE_TURNING_OFF, 1,
+                AdapterState.BLE_START_TIMEOUT_DELAY + CONTEXT_SWITCH_MS);
+
+        // Stop GATT
+        verify(mMockContext, timeout(AdapterState.BLE_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS)
+                .times(2)).startService(any());
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
+                CONTEXT_SWITCH_MS);
+
+        Assert.assertFalse(mAdapterService.isEnabled());
+    }
+
+    /**
+     * Test: Don't stop GATT
+     * Check whether the AdapterService quits gracefully
+     */
+    @Test
+    public void testGattStopTimeout() {
+        doEnable(0, false);
+        Assert.assertTrue(mAdapterService.isEnabled());
+
+        mAdapterService.disable();
+
+        verifyStateChange(BluetoothAdapter.STATE_ON, BluetoothAdapter.STATE_TURNING_OFF, 1,
+                CONTEXT_SWITCH_MS);
+
+        // Stop PBAP and PAN
+        verify(mMockContext, timeout(ONE_SECOND_MS).times(5)).startService(any());
+        mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF);
+        mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_OFF);
+
+        verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, 1,
+                CONTEXT_SWITCH_MS);
+
+        mAdapterService.onBrEdrDown();
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF, 1,
+                CONTEXT_SWITCH_MS);
+
+        // Stop GATT
+        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);
+
+        Assert.assertFalse(mAdapterService.isEnabled());
+    }
+
+    /**
+     * Test: Don't start a classic profile
+     * Check whether the AdapterService quits gracefully
+     */
+    @Test
+    public void testProfileStartTimeout() {
+        Assert.assertFalse(mAdapterService.isEnabled());
+
+        mAdapterService.enable();
+
+        verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON, 1,
+                CONTEXT_SWITCH_MS);
+
+        // Start GATT
+        verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(1)).startService(any());
+        mAdapterService.addProfile(mMockGattService);
+        mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_ON);
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON, 1,
+                NATIVE_INIT_MS);
+
+        mAdapterService.onLeServiceUp();
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON, 1,
+                CONTEXT_SWITCH_MS);
+
+        // Register Mock PBAP and PAN services, only start one
+        verify(mMockContext, timeout(ONE_SECOND_MS).times(3)).startService(any());
+        mAdapterService.addProfile(mMockService);
+        mAdapterService.addProfile(mMockService2);
+        mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_ON);
+
+        verifyStateChange(BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_TURNING_OFF, 1,
+                AdapterState.BREDR_START_TIMEOUT_DELAY + CONTEXT_SWITCH_MS);
+
+        // Stop PBAP and PAN
+        verify(mMockContext, timeout(ONE_SECOND_MS).times(5)).startService(any());
+        mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF);
+
+        verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, 1,
+                CONTEXT_SWITCH_MS);
+    }
+
+    /**
+     * Test: Don't stop a classic profile
+     * Check whether the AdapterService quits gracefully
+     */
+    @Test
+    public void testProfileStopTimeout() {
+        doEnable(0, false);
+
+        Assert.assertTrue(mAdapterService.isEnabled());
+
+        mAdapterService.disable();
+
+        verifyStateChange(BluetoothAdapter.STATE_ON, BluetoothAdapter.STATE_TURNING_OFF, 1,
+                CONTEXT_SWITCH_MS);
+
+        // Stop PBAP and PAN
+        verify(mMockContext, timeout(ONE_SECOND_MS).times(5)).startService(any());
+        mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF);
+
+        verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF,
+                BluetoothAdapter.STATE_BLE_TURNING_OFF, 1,
+                AdapterState.BREDR_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS);
+
+        // Stop GATT
+        verify(mMockContext, timeout(ONE_SECOND_MS).times(6)).startService(any());
+        mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF);
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
+                AdapterState.BLE_STOP_TIMEOUT_DELAY + CONTEXT_SWITCH_MS);
+
+        Assert.assertFalse(mAdapterService.isEnabled());
+    }
+
+    /**
+     * Test: Toggle snoop logging setting
+     * Check whether the AdapterService restarts fully
+     */
+    @Test
+    public void testSnoopLoggingChange() {
+        String snoopSetting =
+                SystemProperties.get(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, "");
+        SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, "false");
+        doEnable(0, false);
+
+        Assert.assertTrue(mAdapterService.isEnabled());
+
+        Assert.assertFalse(
+                SystemProperties.getBoolean(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY,
+                        true));
+
+        SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, "true");
+
+        mAdapterService.disable();
+
+        verifyStateChange(BluetoothAdapter.STATE_ON, BluetoothAdapter.STATE_TURNING_OFF, 1,
+                CONTEXT_SWITCH_MS);
+
+        // Stop PBAP and PAN
+        verify(mMockContext, timeout(ONE_SECOND_MS).times(5)).startService(any());
+        mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_OFF);
+        mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_OFF);
+
+        verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, 1,
+                CONTEXT_SWITCH_MS);
+
+        // Don't call onBrEdrDown().  The Adapter should turn itself off.
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF, 1,
+                CONTEXT_SWITCH_MS);
+
+        // Stop GATT
+        verify(mMockContext, timeout(ONE_SECOND_MS).times(6)).startService(any());
+        mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_OFF);
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
+                CONTEXT_SWITCH_MS);
+
+        Assert.assertFalse(mAdapterService.isEnabled());
+
+        // Restore earlier setting
+        SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_ENABLE_PROPERTY, snoopSetting);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java b/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
new file mode 100644
index 0000000..29585ff
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
@@ -0,0 +1,315 @@
+/*
+ * 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.btservice;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+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 com.android.bluetooth.TestUtils;
+
+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 BondStateMachineTest {
+    private static final int TEST_BOND_REASON = 0;
+    private static final byte[] TEST_BT_ADDR_BYTES = {00, 11, 22, 33, 44, 55};
+    private static final ParcelUuid[] TEST_UUIDS =
+            {ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB")};
+
+    private static final int BOND_NONE = BluetoothDevice.BOND_NONE;
+    private static final int BOND_BONDING = BluetoothDevice.BOND_BONDING;
+    private static final int BOND_BONDED = BluetoothDevice.BOND_BONDED;
+
+    private AdapterProperties mAdapterProperties;
+    private BluetoothDevice mDevice;
+    private Context mTargetContext;
+    private RemoteDevices mRemoteDevices;
+    private BondStateMachine mBondStateMachine;
+    private HandlerThread mHandlerThread;
+    private RemoteDevices.DeviceProperties mDeviceProperties;
+    private int mVerifyCount = 0;
+
+    @Mock private AdapterService mAdapterService;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        mHandlerThread = new HandlerThread("BondStateMachineTestHandlerThread");
+        mHandlerThread.start();
+
+        mRemoteDevices = new RemoteDevices(mAdapterService, mHandlerThread.getLooper());
+        mRemoteDevices.reset();
+        when(mAdapterService.getResources()).thenReturn(
+                mTargetContext.getResources());
+        mAdapterProperties = new AdapterProperties(mAdapterService);
+        mAdapterProperties.init(mRemoteDevices);
+        mBondStateMachine = BondStateMachine.make(mAdapterService, mAdapterProperties,
+                mRemoteDevices);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testSendIntent() {
+        int badBondState = 42;
+        mVerifyCount = 0;
+
+        // Reset mRemoteDevices for the test.
+        mRemoteDevices.reset();
+        mDeviceProperties = mRemoteDevices.addDeviceProperties(TEST_BT_ADDR_BYTES);
+        mDevice = mDeviceProperties.getDevice();
+        Assert.assertNotNull(mDevice);
+
+        /* Classic / Dualmode test cases*/
+        // Uuid not available, mPendingBondedDevice is empty.
+        testSendIntentNoPendingDevice(BOND_NONE, BOND_NONE, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDevice(BOND_NONE, BOND_BONDING, BOND_BONDING,
+                true, BOND_NONE, BOND_BONDING);
+        testSendIntentNoPendingDevice(BOND_NONE, BOND_BONDED, BOND_BONDED,
+                true, BOND_NONE, BOND_BONDING);
+        testSendIntentNoPendingDevice(BOND_NONE, badBondState, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDevice(BOND_BONDING, BOND_NONE, BOND_NONE,
+                true, BOND_BONDING, BOND_NONE);
+        testSendIntentNoPendingDevice(BOND_BONDING, BOND_BONDING, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDevice(BOND_BONDING, BOND_BONDED, BOND_BONDED,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDevice(BOND_BONDING, badBondState, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDevice(BOND_BONDED, BOND_NONE, BOND_NONE,
+                true, BOND_BONDED, BOND_NONE);
+        testSendIntentNoPendingDevice(BOND_BONDED, BOND_BONDING, BOND_BONDING,
+                true, BOND_BONDED, BOND_BONDING);
+        testSendIntentNoPendingDevice(BOND_BONDED, BOND_BONDED, BOND_BONDED,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDevice(BOND_BONDED, badBondState, BOND_BONDED,
+                false, BOND_NONE, BOND_NONE);
+
+        // Uuid not available, mPendingBondedDevice contains a remote device.
+        testSendIntentPendingDevice(BOND_NONE, BOND_NONE, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_NONE, BOND_BONDING, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_NONE, BOND_BONDED, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_NONE, badBondState, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_BONDING, BOND_NONE, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_BONDING, BOND_BONDING, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_BONDING, BOND_BONDED, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_BONDING, badBondState, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_BONDED, BOND_NONE, BOND_NONE,
+                true, BOND_BONDING, BOND_NONE);
+        testSendIntentPendingDevice(BOND_BONDED, BOND_BONDING, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_BONDED, BOND_BONDED, BOND_BONDED,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDevice(BOND_BONDED, badBondState, BOND_BONDED,
+                false, BOND_NONE, BOND_NONE);
+
+        // Uuid available, mPendingBondedDevice is empty.
+        testSendIntentNoPendingDeviceWithUuid(BOND_NONE, BOND_NONE, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDeviceWithUuid(BOND_NONE, BOND_BONDING, BOND_BONDING,
+                true, BOND_NONE, BOND_BONDING);
+        testSendIntentNoPendingDeviceWithUuid(BOND_NONE, BOND_BONDED, BOND_BONDED,
+                true, BOND_NONE, BOND_BONDED);
+        testSendIntentNoPendingDeviceWithUuid(BOND_NONE, badBondState, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDeviceWithUuid(BOND_BONDING, BOND_NONE, BOND_NONE,
+                true, BOND_BONDING, BOND_NONE);
+        testSendIntentNoPendingDeviceWithUuid(BOND_BONDING, BOND_BONDING, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDeviceWithUuid(BOND_BONDING, BOND_BONDED, BOND_BONDED,
+                true, BOND_BONDING, BOND_BONDED);
+        testSendIntentNoPendingDeviceWithUuid(BOND_BONDING, badBondState, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDeviceWithUuid(BOND_BONDED, BOND_NONE, BOND_NONE,
+                true, BOND_BONDED, BOND_NONE);
+        testSendIntentNoPendingDeviceWithUuid(BOND_BONDED, BOND_BONDING, BOND_BONDING,
+                true, BOND_BONDED, BOND_BONDING);
+        testSendIntentNoPendingDeviceWithUuid(BOND_BONDED, BOND_BONDED, BOND_BONDED,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentNoPendingDeviceWithUuid(BOND_BONDED, badBondState, BOND_BONDED,
+                false, BOND_NONE, BOND_NONE);
+
+        // Uuid available, mPendingBondedDevice contains a remote device.
+        testSendIntentPendingDeviceWithUuid(BOND_NONE, BOND_NONE, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_NONE, BOND_BONDING, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_NONE, BOND_BONDED, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_NONE, badBondState, BOND_NONE,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_BONDING, BOND_NONE, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_BONDING, BOND_BONDING, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_BONDING, BOND_BONDED, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_BONDING, badBondState, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_BONDED, BOND_NONE, BOND_NONE,
+                true, BOND_BONDING, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_BONDED, BOND_BONDING, BOND_BONDING,
+                false, BOND_NONE, BOND_NONE);
+        testSendIntentPendingDeviceWithUuid(BOND_BONDED, BOND_BONDED, BOND_BONDED,
+                true, BOND_BONDING, BOND_BONDED);
+        testSendIntentPendingDeviceWithUuid(BOND_BONDED, badBondState, BOND_BONDED,
+                false, BOND_NONE, BOND_NONE);
+
+        /* Low energy test cases */
+        testSendIntentBle(BOND_NONE, BOND_NONE, BOND_NONE);
+        testSendIntentBle(BOND_NONE, BOND_BONDING, BOND_BONDING);
+        testSendIntentBle(BOND_NONE, BOND_BONDED, BOND_BONDED);
+        testSendIntentBle(BOND_BONDING, BOND_NONE, BOND_NONE);
+        testSendIntentBle(BOND_BONDING, BOND_BONDING, BOND_BONDING);
+        testSendIntentBle(BOND_BONDING, BOND_BONDED, BOND_BONDED);
+        testSendIntentBle(BOND_BONDED, BOND_NONE, BOND_NONE);
+        testSendIntentBle(BOND_BONDED, BOND_BONDING, BOND_BONDING);
+        testSendIntentBle(BOND_BONDED, BOND_BONDED, BOND_BONDED);
+    }
+
+    private void testSendIntentCase(int oldState, int newState, int expectedNewState,
+            boolean shouldBroadcast, int broadcastOldState, int broadcastNewState) {
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+
+        // Setup old state before start test.
+        mDeviceProperties.mBondState = oldState;
+
+        try {
+            mBondStateMachine.sendIntent(mDevice, newState, TEST_BOND_REASON);
+        } catch (IllegalArgumentException e) {
+            // Do nothing.
+        }
+        Assert.assertEquals(expectedNewState, mDeviceProperties.getBondState());
+
+        // Check for bond state Intent status.
+        if (shouldBroadcast) {
+            verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
+                    intentArgument.capture(), eq(UserHandle.ALL),
+                    eq(AdapterService.BLUETOOTH_PERM));
+            verifyBondStateChangeIntent(broadcastOldState, broadcastNewState,
+                    intentArgument.getValue());
+        } else {
+            verify(mAdapterService, times(mVerifyCount)).sendBroadcastAsUser(any(Intent.class),
+                    any(UserHandle.class), anyString());
+        }
+    }
+
+    private void testSendIntentNoPendingDeviceWithUuid(int oldState, int newState,
+            int expectedNewState, boolean shouldBroadcast, int broadcastOldState,
+            int broadcastNewState) {
+        // Add dummy UUID for the device.
+        mDeviceProperties.mUuids = TEST_UUIDS;
+        testSendIntentNoPendingDevice(oldState, newState, expectedNewState, shouldBroadcast,
+                broadcastOldState, broadcastNewState);
+    }
+
+    private void testSendIntentPendingDeviceWithUuid(int oldState, int newState,
+            int expectedNewState, boolean shouldBroadcast, int broadcastOldState,
+            int broadcastNewState) {
+        // Add dummy UUID for the device.
+        mDeviceProperties.mUuids = TEST_UUIDS;
+        testSendIntentPendingDevice(oldState, newState, expectedNewState, shouldBroadcast,
+                broadcastOldState, broadcastNewState);
+    }
+
+    private void testSendIntentPendingDevice(int oldState, int newState, int expectedNewState,
+            boolean shouldBroadcast, int broadcastOldState, int broadcastNewState) {
+        // Test for classic remote device.
+        mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_CLASSIC;
+        mBondStateMachine.mPendingBondedDevices.clear();
+        mBondStateMachine.mPendingBondedDevices.add(mDevice);
+        testSendIntentCase(oldState, newState, expectedNewState, shouldBroadcast,
+                broadcastOldState, broadcastNewState);
+
+        // Test for dual-mode remote device.
+        mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_DUAL;
+        mBondStateMachine.mPendingBondedDevices.clear();
+        mBondStateMachine.mPendingBondedDevices.add(mDevice);
+        testSendIntentCase(oldState, newState, expectedNewState, shouldBroadcast,
+                broadcastOldState, broadcastNewState);
+    }
+
+    private void testSendIntentNoPendingDevice(int oldState, int newState, int expectedNewState,
+            boolean shouldBroadcast, int broadcastOldState, int broadcastNewState) {
+        // Test for classic remote device.
+        mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_CLASSIC;
+        mBondStateMachine.mPendingBondedDevices.clear();
+        testSendIntentCase(oldState, newState, expectedNewState, shouldBroadcast,
+                broadcastOldState, broadcastNewState);
+
+        // Test for dual-mode remote device.
+        mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_DUAL;
+        mBondStateMachine.mPendingBondedDevices.clear();
+        testSendIntentCase(oldState, newState, expectedNewState, shouldBroadcast,
+                broadcastOldState, broadcastNewState);
+    }
+
+    private void testSendIntentBle(int oldState, int newState, int expectedNewState) {
+        // Test for low energy remote device.
+        mDeviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_LE;
+        mBondStateMachine.mPendingBondedDevices.clear();
+        testSendIntentCase(oldState, newState, newState, (oldState != newState),
+                oldState, newState);
+    }
+
+    private void verifyBondStateChangeIntent(int oldState, int newState, Intent intent) {
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(BluetoothDevice.ACTION_BOND_STATE_CHANGED, intent.getAction());
+        Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(newState, intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1));
+        Assert.assertEquals(oldState, intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE,
+                                                          -1));
+        if (newState == BOND_NONE) {
+            Assert.assertEquals(TEST_BOND_REASON, intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
+                                                              -1));
+        } else {
+            Assert.assertEquals(-1, intent.getIntExtra(BluetoothDevice.EXTRA_REASON, -1));
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java b/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
new file mode 100644
index 0000000..b15c7b4
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.btservice;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.BluetoothMetricsProto.BluetoothLog;
+import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
+import com.android.bluetooth.BluetoothMetricsProto.ProfileId;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Unit tests for {@link MetricsLogger}
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MetricsLoggerTest {
+
+    @Before
+    public void setUp() {
+        // Dump metrics to clean up internal states
+        MetricsLogger.dumpProto(BluetoothLog.newBuilder());
+    }
+
+    @After
+    public void tearDown() {
+        // Dump metrics to clean up internal states
+        MetricsLogger.dumpProto(BluetoothLog.newBuilder());
+    }
+
+    /**
+     * Simple test to verify that profile connection event can be logged, dumped, and cleaned
+     */
+    @Test
+    public void testLogProfileConnectionEvent() {
+        MetricsLogger.logProfileConnectionEvent(ProfileId.AVRCP);
+        BluetoothLog.Builder metricsBuilder = BluetoothLog.newBuilder();
+        MetricsLogger.dumpProto(metricsBuilder);
+        BluetoothLog metricsProto = metricsBuilder.build();
+        Assert.assertEquals(1, metricsProto.getProfileConnectionStatsCount());
+        ProfileConnectionStats profileUsageStatsAvrcp = metricsProto.getProfileConnectionStats(0);
+        Assert.assertEquals(ProfileId.AVRCP, profileUsageStatsAvrcp.getProfileId());
+        Assert.assertEquals(1, profileUsageStatsAvrcp.getNumTimesConnected());
+        // Verify that MetricsLogger's internal state is cleared after a dump
+        BluetoothLog.Builder metricsBuilderAfterDump = BluetoothLog.newBuilder();
+        MetricsLogger.dumpProto(metricsBuilderAfterDump);
+        BluetoothLog metricsProtoAfterDump = metricsBuilderAfterDump.build();
+        Assert.assertEquals(0, metricsProtoAfterDump.getProfileConnectionStatsCount());
+    }
+
+    /**
+     * Test whether multiple profile's connection events can be logged interleaving
+     */
+    @Test
+    public void testLogProfileConnectionEventMultipleProfile() {
+        MetricsLogger.logProfileConnectionEvent(ProfileId.AVRCP);
+        MetricsLogger.logProfileConnectionEvent(ProfileId.HEADSET);
+        MetricsLogger.logProfileConnectionEvent(ProfileId.AVRCP);
+        BluetoothLog.Builder metricsBuilder = BluetoothLog.newBuilder();
+        MetricsLogger.dumpProto(metricsBuilder);
+        BluetoothLog metricsProto = metricsBuilder.build();
+        Assert.assertEquals(2, metricsProto.getProfileConnectionStatsCount());
+        HashMap<ProfileId, ProfileConnectionStats> profileConnectionCountMap =
+                getProfileUsageStatsMap(metricsProto.getProfileConnectionStatsList());
+        Assert.assertTrue(profileConnectionCountMap.containsKey(ProfileId.AVRCP));
+        Assert.assertEquals(2,
+                profileConnectionCountMap.get(ProfileId.AVRCP).getNumTimesConnected());
+        Assert.assertTrue(profileConnectionCountMap.containsKey(ProfileId.HEADSET));
+        Assert.assertEquals(1,
+                profileConnectionCountMap.get(ProfileId.HEADSET).getNumTimesConnected());
+        // Verify that MetricsLogger's internal state is cleared after a dump
+        BluetoothLog.Builder metricsBuilderAfterDump = BluetoothLog.newBuilder();
+        MetricsLogger.dumpProto(metricsBuilderAfterDump);
+        BluetoothLog metricsProtoAfterDump = metricsBuilderAfterDump.build();
+        Assert.assertEquals(0, metricsProtoAfterDump.getProfileConnectionStatsCount());
+    }
+
+    private static HashMap<ProfileId, ProfileConnectionStats> getProfileUsageStatsMap(
+            List<ProfileConnectionStats> profileUsageStats) {
+        HashMap<ProfileId, ProfileConnectionStats> profileUsageStatsMap = new HashMap<>();
+        profileUsageStats.forEach(item -> profileUsageStatsMap.put(item.getProfileId(), item));
+        return profileUsageStatsMap;
+    }
+
+}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
new file mode 100644
index 0000000..510b9af
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
@@ -0,0 +1,711 @@
+/*
+ * Copyright (C) 2017 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.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 com.android.bluetooth.TestUtils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.hfp.HeadsetService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PhonePolicyTest {
+    private static final int MAX_CONNECTED_AUDIO_DEVICES = 5;
+    private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250;
+    private static final int CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS = 1000;
+    private static final int CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS =
+            CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS * 3 / 2;
+
+    private HandlerThread mHandlerThread;
+    private BluetoothAdapter mAdapter;
+    private PhonePolicy mPhonePolicy;
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private ServiceFactory mServiceFactory;
+    @Mock private HeadsetService mHeadsetService;
+    @Mock private A2dpService mA2dpService;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        // Stub A2DP and HFP
+        when(mHeadsetService.connect(any(BluetoothDevice.class))).thenReturn(true);
+        when(mA2dpService.connect(any(BluetoothDevice.class))).thenReturn(true);
+        // Prepare the TestUtils
+        TestUtils.setAdapterService(mAdapterService);
+        // Configure the maximum connected audio devices
+        doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices();
+        // Setup the mocked factory to return mocked services
+        doReturn(mHeadsetService).when(mServiceFactory).getHeadsetService();
+        doReturn(mA2dpService).when(mServiceFactory).getA2dpService();
+        // Start handler thread for this test
+        mHandlerThread = new HandlerThread("PhonePolicyTestHandlerThread");
+        mHandlerThread.start();
+        // Mock the looper
+        doReturn(mHandlerThread.getLooper()).when(mAdapterService).getMainLooper();
+        // Tell the AdapterService that it is a mock (see isMock documentation)
+        doReturn(true).when(mAdapterService).isMock();
+        // Must be called to initialize services
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        PhonePolicy.sConnectOtherProfilesTimeoutMillis = CONNECT_OTHER_PROFILES_TIMEOUT_MILLIS;
+        mPhonePolicy = new PhonePolicy(mAdapterService, mServiceFactory);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    /**
+     * Test that when new UUIDs are refreshed for a device then we set the priorities for various
+     * profiles accurately. The following profiles should have ON priorities:
+     *     A2DP, HFP, HID and PAN
+     */
+    @Test
+    public void testProcessInitProfilePriorities() {
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        // Mock the HeadsetService to return undefined priority
+        when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+
+        // Mock the A2DP service to return undefined priority
+        when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+
+        // Inject an event for UUIDs updated for a remote device with only HFP enabled
+        Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        ParcelUuid[] uuids = new ParcelUuid[2];
+        uuids[0] = BluetoothUuid.Handsfree;
+        uuids[1] = BluetoothUuid.AudioSink;
+        intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids);
+        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+
+        // Check that the priorities of the devices for preferred profiles are set to ON
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(device),
+                eq(BluetoothProfile.PRIORITY_ON));
+        verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(device),
+                eq(BluetoothProfile.PRIORITY_ON));
+    }
+
+    /**
+     * Test that when the adapter is turned ON then we call autoconnect on devices that have HFP and
+     * A2DP enabled. NOTE that the assumption is that we have already done the pairing previously
+     * and hence the priorities for the device is already set to AUTO_CONNECT over HFP and A2DP (as
+     * part of post pairing process).
+     */
+    @Test
+    public void testAdapterOnAutoConnect() {
+        // Return desired values from the mocked object(s)
+        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
+        when(mAdapterService.isQuietModeEnabled()).thenReturn(false);
+
+        // 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
+        when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+
+        // Inject an event that the adapter is turned on.
+        Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+        intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
+        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+
+        // Check that we got a request to connect over HFP and A2DP
+        verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevices[0]));
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevices[0]));
+    }
+
+    /**
+     * Test that when an auto connect device is disconnected, its priority is set to ON if its
+     * original priority is auto connect
+     */
+    @Test
+    public void testDisconnectNoAutoConnect() {
+        // Return desired values from the mocked object(s)
+        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
+        when(mAdapterService.isQuietModeEnabled()).thenReturn(false);
+
+        // Return a list of bonded devices (just one)
+        BluetoothDevice[] bondedDevices = new BluetoothDevice[4];
+        bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
+        bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
+        bondedDevices[2] = TestUtils.getTestDevice(mAdapter, 2);
+        bondedDevices[3] = TestUtils.getTestDevice(mAdapter, 3);
+        when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
+
+        // Make all devices auto connect
+        when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        when(mHeadsetService.getPriority(bondedDevices[3])).thenReturn(
+                BluetoothProfile.PRIORITY_OFF);
+
+        // Make one of the device active
+        Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+
+        // All other disconnected device's priority is set to ON, except disabled ones
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0],
+                BluetoothProfile.PRIORITY_ON);
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1],
+                BluetoothProfile.PRIORITY_ON);
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[2],
+                BluetoothProfile.PRIORITY_ON);
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0],
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt());
+        when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+
+        // Make another device active
+        when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+
+        // This device should be set to auto connect while the first device is reset to ON
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
+                bondedDevices[0], BluetoothProfile.PRIORITY_ON);
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1],
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt());
+        when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+
+        // Set active device to null
+        when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) null);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+
+        // This should not have any effect
+        verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setPriority(
+                bondedDevices[1], BluetoothProfile.PRIORITY_ON);
+
+        // 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);
+
+        // This device should be set to ON
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
+                bondedDevices[1], BluetoothProfile.PRIORITY_ON);
+
+        // Verify that we are not setting priorities to random devices and values
+        verify(mHeadsetService, times(7)).setPriority(any(BluetoothDevice.class), anyInt());
+    }
+
+    /**
+     * 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
+    public void testReconnectOnPartialConnect() {
+        // 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 as 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 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);
+
+        // Check that we get a call to A2DP connect
+        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
+                eq(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
+     * connect property works independently so that we will connect to all devices that are in
+     * auto connect mode.
+     */
+    @Test
+    public void testAutoConnectMultipleDevices() {
+        final int kMaxTestDevices = 3;
+        BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices];
+        ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
+        ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>();
+        BluetoothDevice a2dpNotConnectedDevice1 = null;
+        BluetoothDevice a2dpNotConnectedDevice2 = null;
+
+        for (int i = 0; i < kMaxTestDevices; i++) {
+            BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
+            testDevices[i] = testDevice;
+
+            // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles
+            // are auto-connectable.
+            when(mHeadsetService.getPriority(testDevice)).thenReturn(
+                    BluetoothProfile.PRIORITY_AUTO_CONNECT);
+            when(mA2dpService.getPriority(testDevice)).thenReturn(
+                    BluetoothProfile.PRIORITY_AUTO_CONNECT);
+            // 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 as list
+            // of connected devices.
+            hsConnectedDevices.add(testDevice);
+            // Connect A2DP for all devices except the last one
+            if (i < (kMaxTestDevices - 2)) {
+                a2dpConnectedDevices.add(testDevice);
+            }
+        }
+        a2dpNotConnectedDevice1 = hsConnectedDevices.get(kMaxTestDevices - 1);
+        a2dpNotConnectedDevice2 = hsConnectedDevices.get(kMaxTestDevices - 2);
+
+        when(mAdapterService.getBondedDevices()).thenReturn(testDevices);
+        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
+        when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
+        when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices);
+        // Two of the A2DP devices are not connected
+        when(mA2dpService.getConnectionState(a2dpNotConnectedDevice1)).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        when(mA2dpService.getConnectionState(a2dpNotConnectedDevice2)).thenReturn(
+                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 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);
+
+        // 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);
+
+        // Check that we get a call to A2DP connect
+        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
+                eq(a2dpNotConnectedDevice1));
+        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
+                eq(a2dpNotConnectedDevice2));
+    }
+
+    /**
+     * Test that the connect priority of all devices are set as appropriate if there is one
+     * connected device.
+     * - The HFP and A2DP connect priority for connected devices is set to
+     *   BluetoothProfile.PRIORITY_AUTO_CONNECT
+     * - The HFP and A2DP connect priority for bonded devices is set to
+     *   BluetoothProfile.PRIORITY_ON
+     */
+    @Test
+    public void testSetPriorityMultipleDevices() {
+        // testDevices[0] - connected for both HFP and A2DP
+        // testDevices[1] - connected only for HFP - will auto-connect for A2DP
+        // testDevices[2] - connected only for A2DP - will auto-connect for HFP
+        // testDevices[3] - not connected
+        final int kMaxTestDevices = 4;
+        BluetoothDevice[] testDevices = new BluetoothDevice[kMaxTestDevices];
+        ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
+        ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>();
+
+        for (int i = 0; i < kMaxTestDevices; i++) {
+            BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
+            testDevices[i] = testDevice;
+
+            // Connect HFP and A2DP for each device as appropriate.
+            // Return PRIORITY_AUTO_CONNECT only for testDevices[0]
+            if (i == 0) {
+                hsConnectedDevices.add(testDevice);
+                a2dpConnectedDevices.add(testDevice);
+                when(mHeadsetService.getPriority(testDevice)).thenReturn(
+                        BluetoothProfile.PRIORITY_AUTO_CONNECT);
+                when(mA2dpService.getPriority(testDevice)).thenReturn(
+                        BluetoothProfile.PRIORITY_AUTO_CONNECT);
+            }
+            if (i == 1) {
+                hsConnectedDevices.add(testDevice);
+                when(mHeadsetService.getPriority(testDevice)).thenReturn(
+                        BluetoothProfile.PRIORITY_ON);
+                when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
+            }
+            if (i == 2) {
+                a2dpConnectedDevices.add(testDevice);
+                when(mHeadsetService.getPriority(testDevice)).thenReturn(
+                        BluetoothProfile.PRIORITY_ON);
+                when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
+            }
+            if (i == 3) {
+                // Device not connected
+                when(mHeadsetService.getPriority(testDevice)).thenReturn(
+                        BluetoothProfile.PRIORITY_ON);
+                when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
+            }
+        }
+        when(mAdapterService.getBondedDevices()).thenReturn(testDevices);
+        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
+        when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
+        when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices);
+        // Some of the devices are not connected
+        // testDevices[0] - connected for both HFP and A2DP
+        when(mHeadsetService.getConnectionState(testDevices[0])).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        when(mA2dpService.getConnectionState(testDevices[0])).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        // testDevices[1] - connected only for HFP - will auto-connect for A2DP
+        when(mHeadsetService.getConnectionState(testDevices[1])).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        when(mA2dpService.getConnectionState(testDevices[1])).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        // testDevices[2] - connected only for A2DP - will auto-connect for HFP
+        when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        when(mA2dpService.getConnectionState(testDevices[2])).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        // testDevices[3] - not connected
+        when(mHeadsetService.getConnectionState(testDevices[3])).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        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);
+        // Check that we get a call to A2DP connect
+        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
+                eq(testDevices[1]));
+
+        // testDevices[1] auto-connect completed for A2DP
+        a2dpConnectedDevices.add(testDevices[1]);
+        when(mA2dpService.getConnectedDevices()).thenReturn(a2dpConnectedDevices);
+        when(mA2dpService.getConnectionState(testDevices[1])).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+
+        // Check the connect priorities for all devices
+        // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called
+        // - testDevices[1] - connection state changed for HFP should no longer trigger auto
+        //                    connect priority change since it is now triggered by A2DP active
+        //                    device change intent
+        // - testDevices[2] - connected for A2DP: setPriority() should not be called
+        // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called
+        verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt());
+        verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt());
+        verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]),
+                eq(BluetoothProfile.PRIORITY_AUTO_CONNECT));
+        verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt());
+        verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt());
+        verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]), anyInt());
+        verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt());
+        verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt());
+        clearInvocations(mHeadsetService, mA2dpService);
+
+        // 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);
+        // Check that we get a call to HFP connect
+        verify(mHeadsetService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
+                eq(testDevices[2]));
+
+        // testDevices[2] auto-connect completed for HFP
+        hsConnectedDevices.add(testDevices[2]);
+        when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
+        when(mHeadsetService.getConnectionState(testDevices[2])).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+
+        // Check the connect priorities for all devices
+        // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called
+        // - testDevices[1] - connected for HFP and A2DP: setPriority() should not be called
+        // - testDevices[2] - connection state changed for A2DP should no longer trigger auto
+        //                    connect priority change since it is now triggered by A2DP
+        //                    active device change intent
+        // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called
+        verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt());
+        verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt());
+        verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]), anyInt());
+        verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt());
+        verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt());
+        verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]),
+                eq(BluetoothProfile.PRIORITY_AUTO_CONNECT));
+        verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt());
+        verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt());
+        clearInvocations(mHeadsetService, mA2dpService);
+    }
+
+    /**
+     * Test that we will not try to reconnect on a profile if all the connections failed
+     */
+    @Test
+    public void testNoReconnectOnNoConnect() {
+        // 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);
+
+        // Return an empty list simulating that the above connection successful was nullified
+        when(mHeadsetService.getConnectedDevices()).thenReturn(Collections.emptyList());
+        when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList());
+
+        // Both A2DP and HFP should say this device is not connected, except for the intent
+        when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn(
+                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 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);
+
+        // Check that we don't get any calls to reconnect
+        verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect(
+                eq(bondedDevices[0]));
+        verify(mHeadsetService, never()).connect(eq(bondedDevices[0]));
+    }
+
+    /**
+     * Test that we will not try to reconnect on a profile if all the connections failed
+     * with multiple devices
+     */
+    @Test
+    public void testNoReconnectOnNoConnect_MultiDevice() {
+        // Return a list of bonded devices (just one)
+        BluetoothDevice[] bondedDevices = new BluetoothDevice[2];
+        bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
+        bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
+        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(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        when(mA2dpService.getPriority(bondedDevices[1])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+
+        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
+
+        // Return an a list with only the second device as connected
+        when(mHeadsetService.getConnectedDevices()).thenReturn(
+                Collections.singletonList(bondedDevices[1]));
+        when(mA2dpService.getConnectedDevices()).thenReturn(
+                Collections.singletonList(bondedDevices[1]));
+
+        // Both A2DP and HFP should say this device is not connected, except for the intent
+        when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        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[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);
+
+        // Check that we don't get any calls to reconnect
+        verify(mA2dpService, after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).connect(
+                eq(bondedDevices[0]));
+        verify(mHeadsetService, never()).connect(eq(bondedDevices[0]));
+    }
+
+    /**
+     * Test that we will try to connect to other profiles of a device if it is partially connected
+     */
+    @Test
+    public void testReconnectOnPartialConnect_MultiDevice() {
+        // Return a list of bonded devices (just one)
+        BluetoothDevice[] bondedDevices = new BluetoothDevice[2];
+        bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
+        bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
+        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(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+        when(mA2dpService.getPriority(bondedDevices[1])).thenReturn(
+                BluetoothProfile.PRIORITY_AUTO_CONNECT);
+
+        when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
+
+        // Return an a list with only the second device as connected
+        when(mHeadsetService.getConnectedDevices()).thenReturn(
+                Collections.singletonList(bondedDevices[1]));
+        when(mA2dpService.getConnectedDevices()).thenReturn(Collections.emptyList());
+
+        // Both A2DP and HFP should say this device is not connected, except for the intent
+        when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        when(mHeadsetService.getConnectionState(bondedDevices[0])).thenReturn(
+                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);
+
+        // Check that we don't get any calls to reconnect
+        verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
+                eq(bondedDevices[1]));
+    }
+
+    /**
+     * Test that a device with no supported uuids is initialized properly and does not crash the
+     * stack
+     */
+    @Test
+    public void testNoSupportedUuids() {
+        // Mock the HeadsetService to return undefined priority
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+
+        // Mock the A2DP service to return undefined priority
+        when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+
+        // Inject an event for UUIDs updated for a remote device with only HFP enabled
+        Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+
+        // Put no UUIDs
+        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+
+        // Check that we do not crash and not call any setPriority methods
+        verify(mHeadsetService,
+                after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).setPriority(eq(device),
+                eq(BluetoothProfile.PRIORITY_ON));
+        verify(mA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
new file mode 100644
index 0000000..becb2f2
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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.btservice;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+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 com.android.bluetooth.TestUtils;
+
+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.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ProfileServiceTest {
+    private static final int PROFILE_START_MILLIS = 1250;
+    private static final int NUM_REPEATS = 5;
+
+    @Rule public final ServiceTestRule mServiceTestRule = new ServiceTestRule();
+
+    private void setProfileState(Class profile, int state) throws TimeoutException {
+        Intent startIntent = new Intent(InstrumentationRegistry.getTargetContext(), profile);
+        startIntent.putExtra(AdapterService.EXTRA_ACTION,
+                AdapterService.ACTION_SERVICE_STATE_CHANGED);
+        startIntent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
+        mServiceTestRule.startService(startIntent);
+    }
+
+    private void setAllProfilesState(int state, int invocationNumber) throws TimeoutException {
+        for (Class profile : mProfiles) {
+            setProfileState(profile, state);
+        }
+        ArgumentCaptor<ProfileService> argument = ArgumentCaptor.forClass(ProfileService.class);
+        verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times(
+                mProfiles.length * invocationNumber)).onProfileServiceStateChanged(
+                argument.capture(), eq(state));
+        List<ProfileService> argumentProfiles = argument.getAllValues();
+        for (Class profile : mProfiles) {
+            int matches = 0;
+            for (ProfileService arg : argumentProfiles) {
+                if (arg.getClass().getName().equals(profile.getName())) {
+                    matches += 1;
+                }
+            }
+            Assert.assertEquals(invocationNumber, matches);
+        }
+    }
+
+    private @Mock AdapterService mMockAdapterService;
+
+    private Class[] mProfiles;
+
+    @Before
+    public void setUp()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+
+        MockitoAnnotations.initMocks(this);
+
+        mProfiles = Config.getSupportedProfiles();
+
+        mMockAdapterService.initNative(false);
+
+        TestUtils.setAdapterService(mMockAdapterService);
+
+        Assert.assertNotNull(AdapterService.getAdapterService());
+    }
+
+    @After
+    public void tearDown()
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        mMockAdapterService.cleanupNative();
+        TestUtils.clearAdapterService(mMockAdapterService);
+        mMockAdapterService = null;
+        mProfiles = null;
+    }
+
+    /**
+     * Test: Start the Bluetooth services that are configured.
+     * Verify that the same services start.
+     */
+    @Test
+    public void testEnableDisable() throws TimeoutException {
+        setAllProfilesState(BluetoothAdapter.STATE_ON, 1);
+        setAllProfilesState(BluetoothAdapter.STATE_OFF, 1);
+    }
+
+    /**
+     * Test: Start the Bluetooth services that are configured twice.
+     * Verify that the services start.
+     */
+    @Test
+    public void testEnableDisableTwice() throws TimeoutException {
+        setAllProfilesState(BluetoothAdapter.STATE_ON, 1);
+        setAllProfilesState(BluetoothAdapter.STATE_OFF, 1);
+        setAllProfilesState(BluetoothAdapter.STATE_ON, 2);
+        setAllProfilesState(BluetoothAdapter.STATE_OFF, 2);
+    }
+
+    /**
+     * Test: Start the Bluetooth services that are configured.
+     * Verify that each profile starts and stops.
+     */
+    @Test
+    public void testEnableDisableInterleaved() throws TimeoutException {
+        for (Class profile : mProfiles) {
+            setProfileState(profile, BluetoothAdapter.STATE_ON);
+            setProfileState(profile, BluetoothAdapter.STATE_OFF);
+        }
+        ArgumentCaptor<ProfileService> starts = ArgumentCaptor.forClass(ProfileService.class);
+        ArgumentCaptor<ProfileService> stops = ArgumentCaptor.forClass(ProfileService.class);
+        int invocationNumber = mProfiles.length;
+        verify(mMockAdapterService,
+                timeout(PROFILE_START_MILLIS).times(invocationNumber)).onProfileServiceStateChanged(
+                starts.capture(), eq(BluetoothAdapter.STATE_ON));
+        verify(mMockAdapterService,
+                timeout(PROFILE_START_MILLIS).times(invocationNumber)).onProfileServiceStateChanged(
+                stops.capture(), eq(BluetoothAdapter.STATE_OFF));
+
+        List<ProfileService> startedArguments = starts.getAllValues();
+        List<ProfileService> stoppedArguments = stops.getAllValues();
+        Assert.assertEquals(startedArguments.size(), stoppedArguments.size());
+        for (ProfileService service : startedArguments) {
+            Assert.assertTrue(stoppedArguments.contains(service));
+            stoppedArguments.remove(service);
+            Assert.assertFalse(stoppedArguments.contains(service));
+        }
+    }
+
+    /**
+     * Test: Start and stop a single profile repeatedly.
+     * Verify that the profiles start and stop.
+     */
+    @Test
+    public void testRepeatedEnableDisableSingly() throws TimeoutException {
+        int profileNumber = 0;
+        for (Class profile : mProfiles) {
+            for (int i = 0; i < NUM_REPEATS; i++) {
+                setProfileState(profile, BluetoothAdapter.STATE_ON);
+                ArgumentCaptor<ProfileService> start =
+                        ArgumentCaptor.forClass(ProfileService.class);
+                verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times(
+                        NUM_REPEATS * profileNumber + i + 1)).onProfileServiceStateChanged(
+                        start.capture(), eq(BluetoothAdapter.STATE_ON));
+                setProfileState(profile, BluetoothAdapter.STATE_OFF);
+                ArgumentCaptor<ProfileService> stop = ArgumentCaptor.forClass(ProfileService.class);
+                verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times(
+                        NUM_REPEATS * profileNumber + i + 1)).onProfileServiceStateChanged(
+                        stop.capture(), eq(BluetoothAdapter.STATE_OFF));
+                Assert.assertEquals(start.getValue(), stop.getValue());
+            }
+            profileNumber += 1;
+        }
+    }
+
+    /**
+     * Test: Start and stop a single profile repeatedly and verify that the profile services are
+     * registered and unregistered accordingly.
+     */
+    @Test
+    public void testProfileServiceRegisterUnregister() throws TimeoutException {
+        int profileNumber = 0;
+        for (Class profile : mProfiles) {
+            for (int i = 0; i < NUM_REPEATS; i++) {
+                setProfileState(profile, BluetoothAdapter.STATE_ON);
+                ArgumentCaptor<ProfileService> start =
+                        ArgumentCaptor.forClass(ProfileService.class);
+                verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times(
+                        NUM_REPEATS * profileNumber + i + 1)).addProfile(
+                        start.capture());
+                setProfileState(profile, BluetoothAdapter.STATE_OFF);
+                ArgumentCaptor<ProfileService> stop = ArgumentCaptor.forClass(ProfileService.class);
+                verify(mMockAdapterService, timeout(PROFILE_START_MILLIS).times(
+                        NUM_REPEATS * profileNumber + i + 1)).removeProfile(
+                        stop.capture());
+                Assert.assertEquals(start.getValue(), stop.getValue());
+            }
+            profileNumber += 1;
+        }
+    }
+}
diff --git a/tests/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
similarity index 78%
rename from tests/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
rename to tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index 2126049..35215ef 100644
--- a/tests/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -1,12 +1,6 @@
 package com.android.bluetooth.btservice;
 
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAssignedNumbers;
@@ -14,12 +8,17 @@
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
 import android.content.Intent;
-import android.os.Looper;
+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 com.android.bluetooth.Utils;
 import com.android.bluetooth.hfp.HeadsetHalConstants;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -30,6 +29,7 @@
 
 import java.util.ArrayList;
 
+@MediumTest
 @RunWith(AndroidJUnit4.class)
 public class RemoteDevicesTest {
     private static final String TEST_BT_ADDR_1 = "00:11:22:33:44:55";
@@ -38,23 +38,37 @@
     private ArgumentCaptor<String> mStringArgument = ArgumentCaptor.forClass(String.class);
     private BluetoothDevice mDevice1;
     private RemoteDevices mRemoteDevices;
+    private HandlerThread mHandlerThread;
+    private TestLooperManager mTestLooperManager;
 
     @Mock private AdapterService mAdapterService;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        if (Looper.myLooper() == null) Looper.prepare();
         mDevice1 = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR_1);
-        mRemoteDevices = new RemoteDevices(mAdapterService);
+        mHandlerThread = new HandlerThread("RemoteDevicesTestHandlerThread");
+        mHandlerThread.start();
+        mTestLooperManager = InstrumentationRegistry.getInstrumentation()
+                .acquireLooperManager(mHandlerThread.getLooper());
+        mRemoteDevices = new RemoteDevices(mAdapterService, mHandlerThread.getLooper());
+    }
+
+    @After
+    public void tearDown() {
+        mTestLooperManager.release();
+        mHandlerThread.quit();
     }
 
     @Test
     public void testSendUuidIntent() {
+        // Verify that a handler message is sent by the method call
         mRemoteDevices.updateUuids(mDevice1);
-        Looper.myLooper().quitSafely();
-        Looper.loop();
+        Message msg = mTestLooperManager.next();
+        Assert.assertNotNull(msg);
 
+        // Verify that executing that message results in a broadcast intent
+        mTestLooperManager.execute(msg);
         verify(mAdapterService).sendBroadcast(any(), anyString());
         verifyNoMoreInteractions(mAdapterService);
     }
@@ -74,8 +88,8 @@
 
         // Verify that user can get battery level after the update
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
-        Assert.assertEquals(
-                mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel);
+        Assert.assertEquals(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(),
+                batteryLevel);
 
         // Verify that update same battery level for the same device does not trigger intent
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
@@ -84,14 +98,14 @@
         // Verify that updating battery level to different value triggers the intent again
         batteryLevel = 15;
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService, times(2))
-                .sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService, times(2)).sendBroadcast(mIntentArgument.capture(),
+                mStringArgument.capture());
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
 
         // Verify that user can get battery level after the update
-        Assert.assertEquals(
-                mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel);
+        Assert.assertEquals(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(),
+                batteryLevel);
 
         verifyNoMoreInteractions(mAdapterService);
     }
@@ -157,17 +171,17 @@
 
         // Verify that user can get battery level after the update
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
-        Assert.assertEquals(
-                mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel);
+        Assert.assertEquals(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(),
+                batteryLevel);
 
         // Verify that resetting battery level changes it back to BluetoothDevice
         // .BATTERY_LEVEL_UNKNOWN
         mRemoteDevices.resetBatteryLevel(mDevice1);
         // Verify BATTERY_LEVEL_CHANGED intent is sent after first reset
-        verify(mAdapterService, times(2))
-                .sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
-        verifyBatteryLevelChangedIntent(
-                mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, mIntentArgument);
+        verify(mAdapterService, times(2)).sendBroadcast(mIntentArgument.capture(),
+                mStringArgument.capture());
+        verifyBatteryLevelChangedIntent(mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+                mIntentArgument);
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
         // Verify value is reset in properties
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
@@ -180,8 +194,8 @@
 
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService, times(3))
-                .sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
+                mStringArgument.capture());
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
 
@@ -203,8 +217,8 @@
 
         // Verify that user can get battery level after the update
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
-        Assert.assertEquals(
-                mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(), batteryLevel);
+        Assert.assertEquals(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(),
+                batteryLevel);
 
         // Verify that resetting battery level changes it back to BluetoothDevice
         // .BATTERY_LEVEL_UNKNOWN
@@ -212,10 +226,10 @@
                 getHeadsetConnectionStateChangedIntent(mDevice1,
                         BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_DISCONNECTED));
         // Verify BATTERY_LEVEL_CHANGED intent is sent after first reset
-        verify(mAdapterService, times(2))
-                .sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
-        verifyBatteryLevelChangedIntent(
-                mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, mIntentArgument);
+        verify(mAdapterService, times(2)).sendBroadcast(mIntentArgument.capture(),
+                mStringArgument.capture());
+        verifyBatteryLevelChangedIntent(mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+                mIntentArgument);
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
         // Verify value is reset in properties
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
@@ -224,8 +238,8 @@
 
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService, times(3))
-                .sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
+                mStringArgument.capture());
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
 
@@ -247,25 +261,25 @@
 
         // Verify that user can get battery level after the update
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
-        Assert.assertEquals(
-                batteryLevel, mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel());
+        Assert.assertEquals(batteryLevel,
+                mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel());
 
         // Verify that when device is completely disconnected, RemoteDevices reset battery level to
         // BluetoothDevice.BATTERY_LEVEL_UNKNOWN
         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
-        mRemoteDevices.aclStateChangeCallback(
-                0, Utils.getByteAddress(mDevice1), AbstractionLayer.BT_ACL_STATE_DISCONNECTED);
+        mRemoteDevices.aclStateChangeCallback(0, Utils.getByteAddress(mDevice1),
+                AbstractionLayer.BT_ACL_STATE_DISCONNECTED);
         verify(mAdapterService).getState();
         verify(mAdapterService).getConnectionState(mDevice1);
         // Verify ACTION_ACL_DISCONNECTED and BATTERY_LEVEL_CHANGED intent are sent
-        verify(mAdapterService, times(3))
-                .sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
+                mStringArgument.capture());
         verifyBatteryLevelChangedIntent(mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
                 mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM,
                 mStringArgument.getAllValues().get(mStringArgument.getAllValues().size() - 2));
-        Assert.assertEquals(
-                BluetoothDevice.ACTION_ACL_DISCONNECTED, mIntentArgument.getValue().getAction());
+        Assert.assertEquals(BluetoothDevice.ACTION_ACL_DISCONNECTED,
+                mIntentArgument.getValue().getAction());
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
         // Verify value is reset in properties
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
@@ -274,8 +288,8 @@
 
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService, times(4))
-                .sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService, times(4)).sendBroadcast(mIntentArgument.capture(),
+                mStringArgument.capture());
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
 
@@ -290,8 +304,8 @@
         Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));
 
         // Verify that ACTION_HF_INDICATORS_VALUE_CHANGED intent updates battery level
-        mRemoteDevices.onHfIndicatorValueChanged(getHfIndicatorIntent(
-                mDevice1, batteryLevel, HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS));
+        mRemoteDevices.onHfIndicatorValueChanged(getHfIndicatorIntent(mDevice1, batteryLevel,
+                HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS));
         verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
@@ -334,11 +348,15 @@
         // Verify that correct ACTION_VENDOR_SPECIFIC_HEADSET_EVENT updates battery level
         mRemoteDevices.onVendorSpecificHeadsetEvent(getVendorSpecificHeadsetEventIntent(
                 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV,
-                BluetoothAssignedNumbers.APPLE, BluetoothHeadset.AT_CMD_TYPE_SET,
-                new Object[] {3,
-                        BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 5,
-                        2, 1, 3, 10},
-                mDevice1));
+                BluetoothAssignedNumbers.APPLE, BluetoothHeadset.AT_CMD_TYPE_SET, new Object[]{
+                        3,
+                        BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL,
+                        5,
+                        2,
+                        1,
+                        3,
+                        10
+                }, mDevice1));
         verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
         verifyBatteryLevelChangedIntent(mDevice1, 60, mIntentArgument);
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
@@ -358,57 +376,71 @@
 
     @Test
     public void testGetBatteryLevelFromAppleBatteryVsc() {
-        Assert.assertEquals(10,
-                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1,
-                        BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL,
-                        0}));
-        Assert.assertEquals(100,
-                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1,
-                        BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL,
-                        9}));
-        Assert.assertEquals(60,
-                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {3,
-                        BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 5,
-                        2, 1, 3, 10}));
+        Assert.assertEquals(10, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[]{
+                1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 0
+        }));
+        Assert.assertEquals(100, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[]{
+                1, BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 9
+        }));
+        Assert.assertEquals(60, RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[]{
+                3,
+                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL,
+                5,
+                2,
+                1,
+                3,
+                10
+        }));
         Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
-                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {3,
-                        BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, 5,
-                        2, 1, 3}));
-        Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
-                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1,
+                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[]{
+                        3,
                         BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL,
-                        10}));
+                        5,
+                        2,
+                        1,
+                        3
+                }));
         Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
-                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1,
+                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[]{
+                        1,
                         BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL,
-                        -1}));
+                        10
+                }));
         Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
-                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1,
+                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[]{
+                        1,
                         BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL,
-                        "5"}));
+                        -1
+                }));
         Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
-                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[] {1, 35, 37}));
+                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[]{
+                        1,
+                        BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL,
+                        "5"
+                }));
+        Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+                RemoteDevices.getBatteryLevelFromAppleBatteryVsc(new Object[]{1, 35, 37}));
         Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
                 RemoteDevices.getBatteryLevelFromAppleBatteryVsc(
-                        new Object[] {1, "WRONG", "WRONG"}));
+                        new Object[]{1, "WRONG", "WRONG"}));
     }
 
-    private static void verifyBatteryLevelChangedIntent(
-            BluetoothDevice device, int batteryLevel, ArgumentCaptor<Intent> intentArgument) {
+    private static void verifyBatteryLevelChangedIntent(BluetoothDevice device, int batteryLevel,
+            ArgumentCaptor<Intent> intentArgument) {
         verifyBatteryLevelChangedIntent(device, batteryLevel, intentArgument.getValue());
     }
 
-    private static void verifyBatteryLevelChangedIntent(
-            BluetoothDevice device, int batteryLevel, Intent intent) {
+    private static void verifyBatteryLevelChangedIntent(BluetoothDevice device, int batteryLevel,
+            Intent intent) {
         Assert.assertEquals(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, intent.getAction());
         Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
-        Assert.assertEquals(
-                batteryLevel, intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, -15));
+        Assert.assertEquals(batteryLevel,
+                intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, -15));
         Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, intent.getFlags());
     }
 
-    private static Intent getHeadsetConnectionStateChangedIntent(
-            BluetoothDevice device, int oldState, int newState) {
+    private static Intent getHeadsetConnectionStateChangedIntent(BluetoothDevice device,
+            int oldState, int newState) {
         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, oldState);
@@ -416,8 +448,8 @@
         return intent;
     }
 
-    private static Intent getHfIndicatorIntent(
-            BluetoothDevice device, int batteryLevel, int indicatorId) {
+    private static Intent getHfIndicatorIntent(BluetoothDevice device, int batteryLevel,
+            int indicatorId) {
         Intent intent = new Intent(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, indicatorId);
diff --git a/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java b/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
new file mode 100644
index 0000000..63b6d4d
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
@@ -0,0 +1,92 @@
+package com.android.bluetooth.gatt;
+
+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 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;
+
+/**
+ * Test cases for {@link GattService}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GattServiceTest {
+    private static final int TIMES_UP_AND_DOWN = 3;
+    private Context mTargetContext;
+    private GattService mService;
+
+    @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 GattService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_gatt));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, GattService.class);
+        mService = GattService.getGattService();
+        Assert.assertNotNull(mService);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_gatt)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, GattService.class);
+        mService = GattService.getGattService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(GattService.getGattService());
+    }
+
+    @Test
+    public void testServiceUpAndDown() throws Exception {
+        for (int i = 0; i < TIMES_UP_AND_DOWN; i++) {
+            GattService gattService = GattService.getGattService();
+            TestUtils.stopService(mServiceRule, GattService.class);
+            mService = GattService.getGattService();
+            Assert.assertNull(mService);
+            gattService.cleanup();
+            TestUtils.clearAdapterService(mAdapterService);
+            reset(mAdapterService);
+            TestUtils.setAdapterService(mAdapterService);
+            TestUtils.startService(mServiceRule, GattService.class);
+            mService = GattService.getGattService();
+            Assert.assertNotNull(mService);
+        }
+    }
+
+    @Test
+    public void testParseBatchTimestamp() {
+        long timestampNanos = mService.parseTimestampNanos(new byte[]{
+                -54, 7
+        });
+        Assert.assertEquals(99700000000L, timestampNanos);
+    }
+
+}
diff --git a/tests/unit/src/com/android/bluetooth/hdp/HealthServiceTest.java b/tests/unit/src/com/android/bluetooth/hdp/HealthServiceTest.java
new file mode 100644
index 0000000..04023fc
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hdp/HealthServiceTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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
new file mode 100644
index 0000000..d4a978f
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -0,0 +1,1041 @@
+/*
+ * 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.hearingaid;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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 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;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeoutException;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class HearingAidServiceTest {
+    private BluetoothAdapter mAdapter;
+    private Context mTargetContext;
+    private HearingAidService mService;
+    private BluetoothDevice mLeftDevice;
+    private BluetoothDevice mRightDevice;
+    private BluetoothDevice mSingleDevice;
+    private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap;
+    private static final int TIMEOUT_MS = 1000;
+
+    private BroadcastReceiver mHearingAidIntentReceiver;
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private HearingAidNativeInterface mNativeInterface;
+    @Mock private AudioManager mAudioManager;
+
+    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    @Before
+    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));
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        TestUtils.setAdapterService(mAdapterService);
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        startService();
+        mService.mHearingAidNativeInterface = mNativeInterface;
+        mService.mAudioManager = mAudioManager;
+
+        // Override the timeout value to speed up the test
+        HearingAidStateMachine.sConnectTimeoutMs = TIMEOUT_MS;    // 1s
+
+        // Set up the Connection State Changed receiver
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+        mHearingAidIntentReceiver = new HearingAidIntentReceiver();
+        mTargetContext.registerReceiver(mHearingAidIntentReceiver, filter);
+
+        // Get a device for testing
+        mLeftDevice = TestUtils.getTestDevice(mAdapter, 0);
+        mRightDevice = TestUtils.getTestDevice(mAdapter, 1);
+        mSingleDevice = TestUtils.getTestDevice(mAdapter, 2);
+        mDeviceQueueMap = new HashMap<>();
+        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)) {
+            return;
+        }
+        stopService();
+        mTargetContext.unregisterReceiver(mHearingAidIntentReceiver);
+        mDeviceQueueMap.clear();
+        TestUtils.clearAdapterService(mAdapterService);
+        reset(mAudioManager);
+    }
+
+    private void startService() throws TimeoutException {
+        TestUtils.startService(mServiceRule, HearingAidService.class);
+        mService = HearingAidService.getHearingAidService();
+        Assert.assertNotNull(mService);
+    }
+
+    private void stopService() throws TimeoutException {
+        TestUtils.stopService(mServiceRule, HearingAidService.class);
+        mService = HearingAidService.getHearingAidService();
+        Assert.assertNull(mService);
+    }
+
+    private class HearingAidIntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
+                try {
+                    BluetoothDevice device = intent.getParcelableExtra(
+                            BluetoothDevice.EXTRA_DEVICE);
+                    Assert.assertNotNull(device);
+                    LinkedBlockingQueue<Intent> queue = mDeviceQueueMap.get(device);
+                    Assert.assertNotNull(queue);
+                    queue.put(intent);
+                } catch (InterruptedException e) {
+                    Assert.fail("Cannot add Intent to the Connection State queue: "
+                            + e.getMessage());
+                }
+            }
+        }
+    }
+
+    private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device,
+            int newState, int prevState) {
+        Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device));
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
+                intent.getAction());
+        Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                -1));
+    }
+
+    private void verifyNoConnectionStateIntent(int timeoutMs, BluetoothDevice device) {
+        Intent intent = TestUtils.waitForNoIntent(timeoutMs, mDeviceQueueMap.get(device));
+        Assert.assertNull(intent);
+    }
+
+    /**
+     * Test getting HearingAid Service: getHearingAidService()
+     */
+    @Test
+    public void testGetHearingAidService() {
+        Assert.assertEquals(mService, HearingAidService.getHearingAidService());
+    }
+
+    /**
+     * Test stop HearingAid Service
+     */
+    @Test
+    public void testStopHearingAidService() {
+        // Prepare: connect
+        connectDevice(mLeftDevice);
+        // HearingAid Service is already running: test stop(). Note: must be done on the main thread
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            public void run() {
+                Assert.assertTrue(mService.stop());
+            }
+        });
+        // Try to restart the service. Note: must be done on the main thread
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            public void run() {
+                Assert.assertTrue(mService.start());
+            }
+        });
+    }
+
+    /**
+     * Test get/set priority for BluetoothDevice
+     */
+    @Test
+    public void testGetSetPriority() {
+        Assert.assertEquals("Initial device priority",
+                BluetoothProfile.PRIORITY_UNDEFINED,
+                mService.getPriority(mLeftDevice));
+
+        Assert.assertTrue(mService.setPriority(mLeftDevice, 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));
+        Assert.assertEquals("Setting device priority to PRIORITY_ON",
+                BluetoothProfile.PRIORITY_ON,
+                mService.getPriority(mLeftDevice));
+
+        Assert.assertTrue(mService.setPriority(mLeftDevice,
+                BluetoothProfile.PRIORITY_AUTO_CONNECT));
+        Assert.assertEquals("Setting device priority to PRIORITY_AUTO_CONNECT",
+                BluetoothProfile.PRIORITY_AUTO_CONNECT,
+                mService.getPriority(mLeftDevice));
+    }
+
+    /**
+     *  Test okToConnect method using various test cases
+     */
+    @Test
+    public void testOkToConnect() {
+        int badPriorityValue = 1024;
+        int badBondState = 42;
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_NONE, badPriorityValue, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDING, badPriorityValue, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDED, badPriorityValue, false);
+        testOkToConnectCase(mSingleDevice,
+                badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToConnectCase(mSingleDevice,
+                badBondState, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mSingleDevice,
+                badBondState, BluetoothProfile.PRIORITY_ON, false);
+        testOkToConnectCase(mSingleDevice,
+                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));
+    }
+
+    /**
+     * Test that an outgoing connection to device that does not have Hearing Aid UUID is rejected
+     */
+    @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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+
+        // Return No UUID
+        doReturn(new ParcelUuid[]{}).when(mAdapterService)
+                .getRemoteUuids(any(BluetoothDevice.class));
+
+        // Send a connect request
+        Assert.assertFalse("Connect expected to fail", mService.connect(mLeftDevice));
+    }
+
+    /**
+     * Test that an outgoing connection to device with PRIORITY_OFF is rejected
+     */
+    @Test
+    public void testOutgoingConnectPriorityOff() {
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        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);
+
+        // Send a connect request
+        Assert.assertFalse("Connect expected to fail", mService.connect(mLeftDevice));
+    }
+
+    /**
+     * Test that an outgoing connection times out
+     */
+    @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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mLeftDevice));
+
+        // Verify the connection state broadcast, and that we are in Disconnected state
+        verifyConnectionStateIntent(HearingAidService.sConnectTimeoutForEachSideMs * 3,
+                mLeftDevice,
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mLeftDevice));
+    }
+
+    /**
+     * Test that the Hearing Aid Service connects to left and right device at the same time.
+     */
+    @Test
+    public void testConnectAPair_connectBothDevices() {
+        // 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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mLeftDevice));
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mRightDevice));
+    }
+
+    /**
+     * Test that the service disconnects the current pair before connecting to another pair.
+     */
+    @Test
+    public void testConnectAnotherPair_disconnectCurrentPair() {
+        // 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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+
+        HearingAidStackEvent connCompletedEvent;
+        // Send a message to trigger connection completed
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mLeftDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mRightDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state for right side
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+
+        // Send a connect request for another pair
+        Assert.assertTrue("Connect failed", mService.connect(mSingleDevice));
+
+        // Verify the connection state broadcast, and that the first pair is in Disconnecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_CONNECTED);
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertFalse(mService.getConnectedDevices().contains(mLeftDevice));
+        Assert.assertFalse(mService.getConnectedDevices().contains(mRightDevice));
+
+        // Verify the connection state broadcast, and that the second device is in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mSingleDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mSingleDevice));
+    }
+
+    /**
+     * Test that the outgoing connect/disconnect and audio switch is successful.
+     */
+    @Test
+    public void testAudioManagerConnectDisconnect() {
+        // 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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
+        Assert.assertTrue("Connect failed", mService.connect(mRightDevice));
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mLeftDevice));
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mRightDevice));
+
+        HearingAidStackEvent connCompletedEvent;
+        // Send a message to trigger connection completed
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mLeftDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+
+        // Send a message to trigger connection completed for right side
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mRightDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state for right side
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mRightDevice));
+
+        // Verify the list of connected devices
+        Assert.assertTrue(mService.getConnectedDevices().contains(mLeftDevice));
+        Assert.assertTrue(mService.getConnectedDevices().contains(mRightDevice));
+
+        // Verify the audio is routed to Hearing Aid Profile
+        verify(mAudioManager).setBluetoothHearingAidDeviceConnectionState(
+                any(BluetoothDevice.class), eq(BluetoothProfile.STATE_CONNECTED),
+                eq(true), eq(0));
+
+        // Send a disconnect request
+        Assert.assertTrue("Disconnect failed", mService.disconnect(mLeftDevice));
+        Assert.assertTrue("Disconnect failed", mService.disconnect(mRightDevice));
+
+        // Verify the connection state broadcast, and that we are in Disconnecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
+                mService.getConnectionState(mLeftDevice));
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
+                mService.getConnectionState(mRightDevice));
+
+        // Send a message to trigger disconnection completed
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mLeftDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Disconnected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_DISCONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mLeftDevice));
+
+        // Send a message to trigger disconnection completed to the right device
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mRightDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Disconnected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_DISCONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mRightDevice));
+
+        // Verify the list of connected devices
+        Assert.assertFalse(mService.getConnectedDevices().contains(mLeftDevice));
+        Assert.assertFalse(mService.getConnectedDevices().contains(mRightDevice));
+
+        // Verify the audio is not routed to Hearing Aid Profile
+        verify(mAudioManager).setBluetoothHearingAidDeviceConnectionState(
+                any(BluetoothDevice.class), eq(BluetoothProfile.STATE_DISCONNECTED),
+                eq(false), eq(0));
+    }
+
+    /**
+     * Test that only CONNECTION_STATE_CONNECTED or CONNECTION_STATE_CONNECTING Hearing Aid stack
+     * events will create a state machine.
+     */
+    @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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+
+        // Hearing Aid stack event: CONNECTION_STATE_CONNECTING - state machine should be created
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+
+        // HearingAid stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
+
+        // stack event: CONNECTION_STATE_CONNECTED - state machine should be created
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+
+        // stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
+
+        // stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created
+        generateUnexpectedConnectionMessageFromNative(mLeftDevice,
+                BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
+
+        // stack event: CONNECTION_STATE_DISCONNECTED - state machine should not be created
+        generateUnexpectedConnectionMessageFromNative(mLeftDevice,
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
+    }
+
+    /**
+     * Test that a state machine in DISCONNECTED state is removed only after the device is unbond.
+     */
+    @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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+
+        // HearingAid stack event: CONNECTION_STATE_CONNECTING - state machine should be created
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+        // Device unbond - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+
+        // HearingAid stack event: CONNECTION_STATE_CONNECTED - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+        // Device unbond - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+
+        // HearingAid stack event: CONNECTION_STATE_DISCONNECTING - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+        // Device unbond - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+
+        // HearingAid stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_DISCONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+        // Device unbond - state machine is removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
+    }
+
+    /**
+     * Test that a CONNECTION_STATE_DISCONNECTED Hearing Aid stack event will remove the state
+     * machine only if the device is unbond.
+     */
+    @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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+
+        // HearingAid stack event: CONNECTION_STATE_CONNECTING - state machine should be created
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+
+        // HearingAid stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+
+        // HearingAid stack event: CONNECTION_STATE_CONNECTING - state machine remains
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+
+        // Device bond state marked as unbond - state machine is not removed
+        doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService)
+                .getBondState(any(BluetoothDevice.class));
+        Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
+
+        // HearingAid stack event: CONNECTION_STATE_DISCONNECTED - state machine is removed
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
+    }
+
+    @Test
+    public void testConnectionStateChangedActiveDevice() {
+        // 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);
+
+        generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertTrue(mService.getActiveDevices().contains(mRightDevice));
+        Assert.assertFalse(mService.getActiveDevices().contains(mLeftDevice));
+
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertTrue(mService.getActiveDevices().contains(mRightDevice));
+        Assert.assertTrue(mService.getActiveDevices().contains(mLeftDevice));
+
+        generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertFalse(mService.getActiveDevices().contains(mRightDevice));
+        Assert.assertTrue(mService.getActiveDevices().contains(mLeftDevice));
+
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertFalse(mService.getActiveDevices().contains(mRightDevice));
+        Assert.assertFalse(mService.getActiveDevices().contains(mLeftDevice));
+    }
+
+    @Test
+    public void testConnectionStateChangedAnotherActiveDevice() {
+        // 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);
+
+        generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertTrue(mService.getActiveDevices().contains(mRightDevice));
+        Assert.assertFalse(mService.getActiveDevices().contains(mLeftDevice));
+
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertTrue(mService.getActiveDevices().contains(mRightDevice));
+        Assert.assertTrue(mService.getActiveDevices().contains(mLeftDevice));
+
+        generateConnectionMessageFromNative(mSingleDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertFalse(mService.getActiveDevices().contains(mRightDevice));
+        Assert.assertFalse(mService.getActiveDevices().contains(mLeftDevice));
+        Assert.assertTrue(mService.getActiveDevices().contains(mSingleDevice));
+    }
+
+    /**
+     * Verify the correctness during first time connection.
+     * Connect to left device -> Get left device hiSyncId -> Connect to right device ->
+     * Get right device hiSyncId -> Both devices should be always connected
+     */
+    @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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+        // Send a connect request for left device
+        Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mLeftDevice));
+
+        HearingAidStackEvent connCompletedEvent;
+        // Send a message to trigger connection completed
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mLeftDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+
+        // Get hiSyncId for left device
+        HearingAidStackEvent hiSyncIdEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
+        hiSyncIdEvent.device = mLeftDevice;
+        hiSyncIdEvent.valueInt1 = 0x02;
+        hiSyncIdEvent.valueLong2 = 0x0101;
+        mService.messageFromNative(hiSyncIdEvent);
+
+        // Send a connect request for right device
+        Assert.assertTrue("Connect failed", mService.connect(mRightDevice));
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mRightDevice));
+        // Verify the left device is still connected
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+
+        // Send a message to trigger connection completed
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mRightDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mRightDevice));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+
+        // Get hiSyncId for right device
+        hiSyncIdEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
+        hiSyncIdEvent.device = mRightDevice;
+        hiSyncIdEvent.valueInt1 = 0x02;
+        hiSyncIdEvent.valueLong2 = 0x0101;
+        mService.messageFromNative(hiSyncIdEvent);
+
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mRightDevice));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+    }
+
+    /**
+     * Get the HiSyncId from native stack after connecting to left device, then connect right
+     */
+    @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);
+        doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mLeftDevice));
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mService.getConnectionState(mRightDevice));
+
+        HearingAidStackEvent connCompletedEvent;
+        // Send a message to trigger connection completed
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mLeftDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+
+        // Get hiSyncId update from native stack
+        getHiSyncIdFromNative();
+        // Send a connect request for right
+        Assert.assertTrue("Connect failed", mService.connect(mRightDevice));
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(mRightDevice));
+        // Verify the left device is still connected
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+
+        // Send a message to trigger connection completed
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mRightDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mRightDevice));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(mLeftDevice));
+    }
+
+    /**
+     * Test that the service can update HiSyncId from native message
+     */
+    @Test
+    public void getHiSyncIdFromNative_addToMap() {
+        getHiSyncIdFromNative();
+        Assert.assertTrue("hiSyncIdMap should contain mLeftDevice",
+                mService.getHiSyncIdMap().containsKey(mLeftDevice));
+        Assert.assertTrue("hiSyncIdMap should contain mRightDevice",
+                mService.getHiSyncIdMap().containsKey(mRightDevice));
+        Assert.assertTrue("hiSyncIdMap should contain mSingleDevice",
+                mService.getHiSyncIdMap().containsKey(mSingleDevice));
+    }
+
+    /**
+     * Test that the service removes the device from HiSyncIdMap when it's unbonded
+     */
+    @Test
+    public void deviceUnbonded_removeHiSyncId() {
+        getHiSyncIdFromNative();
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        Assert.assertFalse("hiSyncIdMap shouldn't contain mLeftDevice",
+                mService.getHiSyncIdMap().containsKey(mLeftDevice));
+    }
+
+    private void connectDevice(BluetoothDevice device) {
+        HearingAidStackEvent connCompletedEvent;
+
+        List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices();
+
+        // Update the device priority so okToConnect() returns true
+        mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        doReturn(true).when(mNativeInterface).connectHearingAid(device);
+        doReturn(true).when(mNativeInterface).disconnectHearingAid(device);
+
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mService.connect(device));
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mService.getConnectionState(device));
+
+        // Send a message to trigger connection completed
+        connCompletedEvent = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = device;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mService.getConnectionState(device));
+
+        // Verify that the device is in the list of connected devices
+        Assert.assertTrue(mService.getConnectedDevices().contains(device));
+        // Verify the list of previously connected devices
+        for (BluetoothDevice prevDevice : prevConnectedDevices) {
+            Assert.assertTrue(mService.getConnectedDevices().contains(prevDevice));
+        }
+    }
+
+    private void generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState,
+            int oldConnectionState) {
+        HearingAidStackEvent stackEvent =
+                new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        stackEvent.device = device;
+        stackEvent.valueInt1 = newConnectionState;
+        mService.messageFromNative(stackEvent);
+        // Verify the connection state broadcast
+        verifyConnectionStateIntent(TIMEOUT_MS, device, newConnectionState, oldConnectionState);
+    }
+
+    private void generateUnexpectedConnectionMessageFromNative(BluetoothDevice device,
+            int newConnectionState, int oldConnectionState) {
+        HearingAidStackEvent stackEvent =
+                new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        stackEvent.device = device;
+        stackEvent.valueInt1 = newConnectionState;
+        mService.messageFromNative(stackEvent);
+        // Verify the connection state broadcast
+        verifyNoConnectionStateIntent(TIMEOUT_MS, device);
+    }
+
+    /**
+     *  Helper function to test okToConnect() method
+     *
+     *  @param device test device
+     *  @param bondState bond state value, could be invalid
+     *  @param priority value, could be invalid, coudl be invalid
+     *  @param expected expected result from okToConnect()
+     */
+    private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
+            boolean expected) {
+        doReturn(bondState).when(mAdapterService).getBondState(device);
+        Assert.assertTrue(mService.setPriority(device, priority));
+        Assert.assertEquals(expected, mService.okToConnect(device));
+    }
+
+    private void getHiSyncIdFromNative() {
+        HearingAidStackEvent event = new HearingAidStackEvent(
+                HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
+        event.device = mLeftDevice;
+        event.valueInt1 = 0x02;
+        event.valueLong2 = 0x0101;
+        mService.messageFromNative(event);
+        event.device = mRightDevice;
+        event.valueInt1 = 0x03;
+        mService.messageFromNative(event);
+        event.device = mSingleDevice;
+        event.valueInt1 = 0x00;
+        event.valueLong2 = 0x0102;
+        mService.messageFromNative(event);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
new file mode 100644
index 0000000..474861e
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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.hearingaid;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+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 com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+
+import org.hamcrest.core.IsInstanceOf;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+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 HearingAidStateMachineTest {
+    private BluetoothAdapter mAdapter;
+    private Context mTargetContext;
+    private HandlerThread mHandlerThread;
+    private HearingAidStateMachine mHearingAidStateMachine;
+    private BluetoothDevice mTestDevice;
+    private static final int TIMEOUT_MS = 1000;
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private HearingAidService mHearingAidService;
+    @Mock private HearingAidNativeInterface mHearingAidNativeInterface;
+
+    @Before
+    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));
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        // Get a device for testing
+        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+
+        // Set up thread and looper
+        mHandlerThread = new HandlerThread("HearingAidStateMachineTestHandlerThread");
+        mHandlerThread.start();
+        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.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hearing_aid)) {
+            return;
+        }
+        mHearingAidStateMachine.doQuit();
+        mHandlerThread.quit();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    /**
+     * Test that default state is disconnected
+     */
+    @Test
+    public void testDefaultDisconnectedState() {
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mHearingAidStateMachine.getConnectionState());
+    }
+
+    /**
+     * Allow/disallow connection to any device.
+     *
+     * @param allow if true, connection is allowed
+     */
+    private void allowConnection(boolean allow) {
+        doReturn(allow).when(mHearingAidService).okToConnect(any(BluetoothDevice.class));
+    }
+
+    /**
+     * Test that an incoming connection with low priority is rejected
+     */
+    @Test
+    public void testIncomingPriorityReject() {
+        allowConnection(false);
+
+        // Inject an event for when incoming connection is requested
+        HearingAidStackEvent connStCh =
+                new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mHearingAidStateMachine.sendMessage(HearingAidStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that no connection state broadcast is executed
+        verify(mHearingAidService, after(TIMEOUT_MS).never()).sendBroadcast(any(Intent.class),
+                anyString());
+        // Check that we are in Disconnected state
+        Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test that an incoming connection with high priority is accepted
+     */
+    @Test
+    public void testIncomingPriorityAccept() {
+        allowConnection(true);
+
+        // Inject an event for when incoming connection is requested
+        HearingAidStackEvent connStCh =
+                new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTING;
+        mHearingAidStateMachine.sendMessage(HearingAidStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHearingAidService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
+                intentArgument1.capture(), anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Connecting state
+        Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HearingAidStateMachine.Connecting.class));
+
+        // Send a message to trigger connection completed
+        HearingAidStackEvent connCompletedEvent =
+                new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mTestDevice;
+        connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
+        mHearingAidStateMachine.sendMessage(HearingAidStateMachine.STACK_EVENT, connCompletedEvent);
+
+        // Verify that the expected number of broadcasts are executed:
+        // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHearingAidService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(
+                intentArgument2.capture(), anyString());
+        // Check that we are in Connected state
+        Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HearingAidStateMachine.Connected.class));
+    }
+
+    /**
+     * Test that an outgoing connection times out
+     */
+    @Test
+    public void testOutgoingTimeout() {
+        allowConnection(true);
+        doReturn(true).when(mHearingAidNativeInterface).connectHearingAid(any(
+                BluetoothDevice.class));
+        doReturn(true).when(mHearingAidNativeInterface).disconnectHearingAid(any(
+                BluetoothDevice.class));
+
+        // Send a connect request
+        mHearingAidStateMachine.sendMessage(HearingAidStateMachine.CONNECT, mTestDevice);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHearingAidService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
+                intentArgument1.capture(),
+                anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Connecting state
+        Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HearingAidStateMachine.Connecting.class));
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHearingAidService, timeout(HearingAidStateMachine.sConnectTimeoutMs * 2).times(
+                2)).sendBroadcast(intentArgument2.capture(), anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Disconnected state
+        Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
+        verify(mHearingAidNativeInterface).addToWhiteList(eq(mTestDevice));
+    }
+
+    /**
+     * Test that an incoming connection times out
+     */
+    @Test
+    public void testIncomingTimeout() {
+        allowConnection(true);
+        doReturn(true).when(mHearingAidNativeInterface).connectHearingAid(any(
+                BluetoothDevice.class));
+        doReturn(true).when(mHearingAidNativeInterface).disconnectHearingAid(any(
+                BluetoothDevice.class));
+
+        // Inject an event for when incoming connection is requested
+        HearingAidStackEvent connStCh =
+                new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTING;
+        mHearingAidStateMachine.sendMessage(HearingAidStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHearingAidService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
+                intentArgument1.capture(),
+                anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Connecting state
+        Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HearingAidStateMachine.Connecting.class));
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHearingAidService, timeout(HearingAidStateMachine.sConnectTimeoutMs * 2).times(
+                2)).sendBroadcast(intentArgument2.capture(), anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Disconnected state
+        Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
new file mode 100644
index 0000000..865a215
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.hfp;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+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 com.android.bluetooth.TestUtils;
+import com.android.internal.telephony.ISub;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+
+/**
+ * Unit test to verify various methods in {@link HeadsetPhoneState}
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class HeadsetPhoneStateTest {
+    @Mock private ISub mISub;
+    @Mock private IBinder mISubBinder;
+    @Mock private HeadsetService mHeadsetService;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private SubscriptionManager mSubscriptionManager;
+    private HeadsetPhoneState mHeadsetPhoneState;
+    private BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+    private HandlerThread mHandlerThread;
+    private HashMap<String, IBinder> mServiceManagerMockedServices = new HashMap<>();
+    private Object mServiceManagerOriginalServices;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        // Mock SubscriptionManager.getDefaultSubscriptionId() to return a valid value
+        when(mISub.getDefaultSubId()).thenReturn(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+        when(mISubBinder.queryLocalInterface(anyString())).thenReturn(mISub);
+        mServiceManagerMockedServices.put("isub", mISubBinder);
+        mServiceManagerOriginalServices =
+                TestUtils.replaceField(ServiceManager.class, "sCache", null,
+                        mServiceManagerMockedServices);
+        // Stub other methods
+        when(mHeadsetService.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(
+                mTelephonyManager);
+        when(mHeadsetService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn(
+                mSubscriptionManager);
+        mHandlerThread = new HandlerThread("HeadsetStateMachineTestHandlerThread");
+        mHandlerThread.start();
+        when(mHeadsetService.getStateMachinesThreadLooper()).thenReturn(mHandlerThread.getLooper());
+        mHeadsetPhoneState = new HeadsetPhoneState(mHeadsetService);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHeadsetPhoneState.cleanup();
+        mHandlerThread.quit();
+        if (mServiceManagerOriginalServices != null) {
+            TestUtils.replaceField(ServiceManager.class, "sCache", null,
+                    mServiceManagerOriginalServices);
+            mServiceManagerOriginalServices = null;
+        }
+
+    }
+
+    /**
+     * Verify that {@link PhoneStateListener#LISTEN_NONE} should result in no subscription
+     */
+    @Test
+    public void testListenForPhoneState_NoneResultsNoListen() {
+        BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_NONE);
+        verifyZeroInteractions(mTelephonyManager);
+    }
+
+    /**
+     * Verify that {@link PhoneStateListener#LISTEN_SERVICE_STATE} should result in corresponding
+     * subscription
+     */
+    @Test
+    public void testListenForPhoneState_ServiceOnly() {
+        BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+        verifyNoMoreInteractions(mTelephonyManager);
+    }
+
+    /**
+     * Verify that {@link PhoneStateListener#LISTEN_SERVICE_STATE} and
+     * {@link PhoneStateListener#LISTEN_SIGNAL_STRENGTHS} should result in corresponding
+     * subscription
+     */
+    @Test
+    public void testListenForPhoneState_ServiceAndSignalStrength() {
+        BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
+        verify(mTelephonyManager).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+        verifyNoMoreInteractions(mTelephonyManager);
+    }
+
+    /**
+     * Verify that partially turnning off {@link PhoneStateListener#LISTEN_SIGNAL_STRENGTHS} update
+     * should only unsubscribe signal strength update
+     */
+    @Test
+    public void testListenForPhoneState_ServiceAndSignalStrengthUpdateTurnOffSignalStrengh() {
+        BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
+        verify(mTelephonyManager).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
+        verify(mTelephonyManager).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+        verifyNoMoreInteractions(mTelephonyManager);
+    }
+
+    /**
+     * Verify that completely disabling all updates should unsubscribe from everything
+     */
+    @Test
+    public void testListenForPhoneState_ServiceAndSignalStrengthUpdateTurnOffAll() {
+        BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
+        verify(mTelephonyManager).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_NONE);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
+        verify(mTelephonyManager).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+        verifyNoMoreInteractions(mTelephonyManager);
+    }
+
+    /**
+     * Verify that when multiple devices tries to subscribe to the same indicator, the same
+     * subscription is not triggered twice. Also, when one of the device is unsubsidised from an
+     * indicator, the other device still remain subscribed.
+     */
+    @Test
+    public void testListenForPhoneState_MultiDevice_AllUpAllDown() {
+        BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
+        BluetoothDevice device2 = TestUtils.getTestDevice(mAdapter, 2);
+        // Enabling updates from first device should trigger subscription
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
+        verify(mTelephonyManager).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+        verifyNoMoreInteractions(mTelephonyManager);
+        // Enabling updates from second device should not trigger the same subscription
+        mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+        verifyNoMoreInteractions(mTelephonyManager);
+        // Disabling updates from first device should not cancel subscription
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_NONE);
+        verifyNoMoreInteractions(mTelephonyManager);
+        // Disabling updates from second device should cancel subscription
+        mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_NONE);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
+        verify(mTelephonyManager).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+        verifyNoMoreInteractions(mTelephonyManager);
+    }
+
+    /**
+     * Verity that two device each partially subscribe to an indicator result in subscription to
+     * both indicators. Also unsubscribing from one indicator from one device will not cause
+     * unrelated indicator from other device from being unsubscribed.
+     */
+    @Test
+    public void testListenForPhoneState_MultiDevice_PartialUpPartialDown() {
+        BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
+        BluetoothDevice device2 = TestUtils.getTestDevice(mAdapter, 2);
+        // Partially enabling updates from first device should trigger partial subscription
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+        verifyNoMoreInteractions(mTelephonyManager);
+        // Partially enabling updates from second device should trigger partial subscription
+        mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
+        verify(mTelephonyManager).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
+        verify(mTelephonyManager).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+        verifyNoMoreInteractions(mTelephonyManager);
+        // Partially disabling updates from first device should not cancel all subscription
+        mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_NONE);
+        verify(mTelephonyManager, times(2)).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
+        verify(mTelephonyManager, times(2)).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+        verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
+        verify(mTelephonyManager, times(2)).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+        verifyNoMoreInteractions(mTelephonyManager);
+        // Partially disabling updates from second device should cancel subscription
+        mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_NONE);
+        verify(mTelephonyManager, times(3)).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
+        verify(mTelephonyManager, times(3)).setRadioIndicationUpdateMode(
+                TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
+                TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
+        verifyNoMoreInteractions(mTelephonyManager);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
new file mode 100644
index 0000000..39249a9
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -0,0 +1,1204 @@
+/*
+ * 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.hfp;
+
+import static org.hamcrest.Matchers.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.IBluetoothHeadset;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.net.Uri;
+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 com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+
+import org.hamcrest.Matchers;
+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 org.mockito.Spy;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A set of integration test that involves both {@link HeadsetService} and
+ * {@link HeadsetStateMachine}
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class HeadsetServiceAndStateMachineTest {
+    private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250;
+    private static final int START_VR_TIMEOUT_MILLIS = 1000;
+    private static final int START_VR_TIMEOUT_WAIT_MILLIS = START_VR_TIMEOUT_MILLIS * 3 / 2;
+    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";
+
+    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    private Context mTargetContext;
+    private HeadsetService mHeadsetService;
+    private IBluetoothHeadset.Stub mHeadsetServiceBinder;
+    private BluetoothAdapter mAdapter;
+    private HeadsetNativeInterface mNativeInterface;
+    private ArgumentCaptor<HeadsetStateMachine> mStateMachineArgument =
+            ArgumentCaptor.forClass(HeadsetStateMachine.class);
+    private HashSet<BluetoothDevice> mBondedDevices = new HashSet<>();
+    private final BlockingQueue<Intent> mConnectionStateChangedQueue = new LinkedBlockingQueue<>();
+    private final BlockingQueue<Intent> mActiveDeviceChangedQueue = new LinkedBlockingQueue<>();
+    private final BlockingQueue<Intent> mAudioStateChangedQueue = new LinkedBlockingQueue<>();
+    private HeadsetIntentReceiver mHeadsetIntentReceiver;
+    private int mOriginalVrTimeoutMs = 5000;
+    private PowerManager.WakeLock mVoiceRecognitionWakeLock;
+
+    private class HeadsetIntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                Assert.fail("Action is null for intent " + intent);
+                return;
+            }
+            switch (action) {
+                case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
+                    try {
+                        mConnectionStateChangedQueue.put(intent);
+                    } catch (InterruptedException e) {
+                        Assert.fail("Cannot add Intent to the Connection State Changed queue: "
+                                + e.getMessage());
+                    }
+                    break;
+                case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
+                    try {
+                        mActiveDeviceChangedQueue.put(intent);
+                    } catch (InterruptedException e) {
+                        Assert.fail("Cannot add Intent to the Active Device Changed queue: "
+                                + e.getMessage());
+                    }
+                    break;
+                case BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED:
+                    try {
+                        mAudioStateChangedQueue.put(intent);
+                    } catch (InterruptedException e) {
+                        Assert.fail("Cannot add Intent to the Audio State Changed queue: "
+                                + e.getMessage());
+                    }
+                    break;
+                default:
+                    Assert.fail("Unknown action " + action);
+            }
+
+        }
+    }
+
+    @Spy private HeadsetObjectsFactory mObjectsFactory = HeadsetObjectsFactory.getInstance();
+    @Mock private AdapterService mAdapterService;
+    @Mock private HeadsetSystemInterface mSystemInterface;
+    @Mock private AudioManager mAudioManager;
+    @Mock private HeadsetPhoneState mPhoneState;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when HeadsetService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp));
+        MockitoAnnotations.initMocks(this);
+        PowerManager powerManager =
+                (PowerManager) mTargetContext.getSystemService(Context.POWER_SERVICE);
+        mVoiceRecognitionWakeLock =
+                powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "VoiceRecognitionTest");
+        TestUtils.setAdapterService(mAdapterService);
+        doReturn(true).when(mAdapterService).isEnabled();
+        doReturn(MAX_HEADSET_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices();
+        doReturn(new ParcelUuid[]{BluetoothUuid.Handsfree}).when(mAdapterService)
+                .getRemoteUuids(any(BluetoothDevice.class));
+        // We cannot mock HeadsetObjectsFactory.getInstance() with Mockito.
+        // Hence we need to use reflection to call a private method to
+        // initialize properly the HeadsetObjectsFactory.sInstance field.
+        Method method = HeadsetObjectsFactory.class.getDeclaredMethod("setInstanceForTesting",
+                HeadsetObjectsFactory.class);
+        method.setAccessible(true);
+        method.invoke(null, mObjectsFactory);
+        // This line must be called to make sure relevant objects are initialized properly
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        // Mock methods in AdapterService
+        doReturn(FAKE_HEADSET_UUID).when(mAdapterService)
+                .getRemoteUuids(any(BluetoothDevice.class));
+        doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
+                .getBondState(any(BluetoothDevice.class));
+        doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when(
+                mAdapterService).getBondedDevices();
+        // Mock system interface
+        doNothing().when(mSystemInterface).init();
+        doNothing().when(mSystemInterface).stop();
+        when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
+        when(mSystemInterface.getAudioManager()).thenReturn(mAudioManager);
+        when(mSystemInterface.activateVoiceRecognition()).thenReturn(true);
+        when(mSystemInterface.deactivateVoiceRecognition()).thenReturn(true);
+        when(mSystemInterface.getVoiceRecognitionWakeLock()).thenReturn(mVoiceRecognitionWakeLock);
+        when(mSystemInterface.isCallIdle()).thenReturn(true);
+        // Mock methods in HeadsetNativeInterface
+        mNativeInterface = spy(HeadsetNativeInterface.getInstance());
+        doNothing().when(mNativeInterface).init(anyInt(), anyBoolean());
+        doNothing().when(mNativeInterface).cleanup();
+        doReturn(true).when(mNativeInterface).connectHfp(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHfp(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).connectAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).setActiveDevice(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).sendBsir(any(BluetoothDevice.class), anyBoolean());
+        doReturn(true).when(mNativeInterface).startVoiceRecognition(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).stopVoiceRecognition(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface)
+                .atResponseCode(any(BluetoothDevice.class), anyInt(), anyInt());
+        // Use real state machines here
+        doCallRealMethod().when(mObjectsFactory)
+                .makeStateMachine(any(), any(), any(), any(), any(), any());
+        // Mock methods in HeadsetObjectsFactory
+        doReturn(mSystemInterface).when(mObjectsFactory).makeSystemInterface(any());
+        doReturn(mNativeInterface).when(mObjectsFactory).getNativeInterface();
+        Intents.init();
+        // Modify start VR timeout to a smaller value for testing
+        mOriginalVrTimeoutMs = HeadsetService.sStartVrTimeoutMs;
+        HeadsetService.sStartVrTimeoutMs = START_VR_TIMEOUT_MILLIS;
+        TestUtils.startService(mServiceRule, HeadsetService.class);
+        mHeadsetService = HeadsetService.getHeadsetService();
+        Assert.assertNotNull(mHeadsetService);
+        verify(mObjectsFactory).makeSystemInterface(mHeadsetService);
+        verify(mObjectsFactory).getNativeInterface();
+        verify(mNativeInterface).init(MAX_HEADSET_CONNECTIONS + 1, true /* inband ringtone */);
+        mHeadsetServiceBinder = (IBluetoothHeadset.Stub) mHeadsetService.initBinder();
+        Assert.assertNotNull(mHeadsetServiceBinder);
+
+        // Set up the Connection State Changed receiver
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+        filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+        mHeadsetIntentReceiver = new HeadsetIntentReceiver();
+        mTargetContext.registerReceiver(mHeadsetIntentReceiver, filter);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp)) {
+            return;
+        }
+        mTargetContext.unregisterReceiver(mHeadsetIntentReceiver);
+        TestUtils.stopService(mServiceRule, HeadsetService.class);
+        HeadsetService.sStartVrTimeoutMs = mOriginalVrTimeoutMs;
+        Intents.release();
+        mHeadsetService = HeadsetService.getHeadsetService();
+        Assert.assertNull(mHeadsetService);
+        Method method = HeadsetObjectsFactory.class.getDeclaredMethod("setInstanceForTesting",
+                HeadsetObjectsFactory.class);
+        method.setAccessible(true);
+        method.invoke(null, (HeadsetObjectsFactory) null);
+        TestUtils.clearAdapterService(mAdapterService);
+        mBondedDevices.clear();
+        mConnectionStateChangedQueue.clear();
+        mActiveDeviceChangedQueue.clear();
+        // Clear classes that is spied on and has static life time
+        clearInvocations(mNativeInterface);
+    }
+
+    /**
+     * Test to verify that HeadsetService can be successfully started
+     */
+    @Test
+    public void testGetHeadsetService() {
+        Assert.assertEquals(mHeadsetService, HeadsetService.getHeadsetService());
+        // Verify default connection and audio states
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mHeadsetService.getConnectionState(device));
+        Assert.assertEquals(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                mHeadsetService.getAudioState(device));
+    }
+
+    /**
+     * Test to verify that {@link HeadsetService#connect(BluetoothDevice)} actually result in a
+     * call to native interface to create HFP
+     */
+    @Test
+    public void testConnectFromApi() {
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        mBondedDevices.add(device);
+        Assert.assertTrue(mHeadsetService.connect(device));
+        verify(mObjectsFactory).makeStateMachine(device,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and
+        // 250ms for processing two messages should be way more than enough. Anything that breaks
+        // this indicate some breakage in other part of Android OS
+        waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED);
+        verify(mNativeInterface).connectHfp(device);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mHeadsetService.getConnectionState(device));
+        Assert.assertEquals(Collections.singletonList(device),
+                mHeadsetService.getDevicesMatchingConnectionStates(
+                        new int[]{BluetoothProfile.STATE_CONNECTING}));
+        // Get feedback from native to put device into connected state
+        HeadsetStackEvent connectedEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, device);
+        mHeadsetService.messageFromNative(connectedEvent);
+        // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and
+        // 250ms for processing two messages should be way more than enough. Anything that breaks
+        // this indicate some breakage in other part of Android OS
+        waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mHeadsetService.getConnectionState(device));
+        Assert.assertEquals(Collections.singletonList(device),
+                mHeadsetService.getDevicesMatchingConnectionStates(
+                        new int[]{BluetoothProfile.STATE_CONNECTED}));
+    }
+
+    /**
+     * Test to verify that {@link BluetoothDevice#ACTION_BOND_STATE_CHANGED} intent with
+     * {@link BluetoothDevice#EXTRA_BOND_STATE} as {@link BluetoothDevice#BOND_NONE} will cause a
+     * disconnected device to be removed from state machine map
+     */
+    @Test
+    public void testUnbondDevice_disconnectBeforeUnbond() {
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        mBondedDevices.add(device);
+        Assert.assertTrue(mHeadsetService.connect(device));
+        verify(mObjectsFactory).makeStateMachine(device,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and
+        // 250ms for processing two messages should be way more than enough. Anything that breaks
+        // this indicate some breakage in other part of Android OS
+        waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED);
+        verify(mNativeInterface).connectHfp(device);
+        // Get feedback from native layer to go back to disconnected state
+        HeadsetStackEvent connectedEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, device);
+        mHeadsetService.messageFromNative(connectedEvent);
+        // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and
+        // 250ms for processing two messages should be way more than enough. Anything that breaks
+        // this indicate some breakage in other part of Android OS
+        waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
+        // Send unbond intent
+        doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService).getBondState(eq(device));
+        Intent unbondIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        unbondIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
+        unbondIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        InstrumentationRegistry.getTargetContext().sendBroadcast(unbondIntent);
+        // Check that the state machine is actually destroyed
+        verify(mObjectsFactory, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).destroyStateMachine(
+                mStateMachineArgument.capture());
+        Assert.assertEquals(device, mStateMachineArgument.getValue().getDevice());
+    }
+
+    /**
+     * Test to verify that if a device can be property disconnected after
+     * {@link BluetoothDevice#ACTION_BOND_STATE_CHANGED} intent with
+     * {@link BluetoothDevice#EXTRA_BOND_STATE} as {@link BluetoothDevice#BOND_NONE} is received.
+     */
+    @Test
+    public void testUnbondDevice_disconnectAfterUnbond() {
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        mBondedDevices.add(device);
+        Assert.assertTrue(mHeadsetService.connect(device));
+        verify(mObjectsFactory).makeStateMachine(device,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and
+        // 250ms for processing two messages should be way more than enough. Anything that breaks
+        // this indicate some breakage in other part of Android OS
+        verify(mNativeInterface, after(ASYNC_CALL_TIMEOUT_MILLIS)).connectHfp(device);
+        waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED);
+        // Get feedback from native layer to go to connected state
+        HeadsetStackEvent connectedEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, device);
+        mHeadsetService.messageFromNative(connectedEvent);
+        // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and
+        // 250ms for processing two messages should be way more than enough. Anything that breaks
+        // this indicate some breakage in other part of Android OS
+        waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mHeadsetService.getConnectionState(device));
+        Assert.assertEquals(Collections.singletonList(device),
+                mHeadsetService.getConnectedDevices());
+        // Send unbond intent
+        doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService).getBondState(eq(device));
+        Intent unbondIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        unbondIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
+        unbondIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        InstrumentationRegistry.getTargetContext().sendBroadcast(unbondIntent);
+        // Check that the state machine is not destroyed
+        verify(mObjectsFactory, after(ASYNC_CALL_TIMEOUT_MILLIS).never()).destroyStateMachine(
+                any());
+        // Now disconnect the device
+        HeadsetStackEvent connectingEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, device);
+        mHeadsetService.messageFromNative(connectingEvent);
+        waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);
+        // Check that the state machine is destroyed after another async call
+        verify(mObjectsFactory, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).destroyStateMachine(
+                mStateMachineArgument.capture());
+        Assert.assertEquals(device, mStateMachineArgument.getValue().getDevice());
+
+    }
+
+    /**
+     * Test the functionality of
+     * {@link BluetoothHeadset#startScoUsingVirtualVoiceCall()} and
+     * {@link BluetoothHeadset#stopScoUsingVirtualVoiceCall()}
+     *
+     * Normal start and stop
+     */
+    @Test
+    public void testVirtualCall_normalStartStop() throws RemoteException {
+        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()));
+        Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
+        BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2);
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        verify(mNativeInterface).setActiveDevice(activeDevice);
+        waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
+        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        // Start virtual call
+        Assert.assertTrue(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall());
+        Assert.assertTrue(mHeadsetService.isVirtualCallStarted());
+        verifyVirtualCallStartSequenceInvocations(connectedDevices);
+        // End virtual call
+        Assert.assertTrue(mHeadsetServiceBinder.stopScoUsingVirtualVoiceCall());
+        Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
+        verifyVirtualCallStopSequenceInvocations(connectedDevices);
+    }
+
+    /**
+     * Test the functionality of
+     * {@link BluetoothHeadset#startScoUsingVirtualVoiceCall()} and
+     * {@link BluetoothHeadset#stopScoUsingVirtualVoiceCall()}
+     *
+     * Virtual call should be preempted by telecom call
+     */
+    @Test
+    public void testVirtualCall_preemptedByTelecomCall() throws RemoteException {
+        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()));
+        Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
+        BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2);
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        verify(mNativeInterface).setActiveDevice(activeDevice);
+        waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
+        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        // Start virtual call
+        Assert.assertTrue(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall());
+        Assert.assertTrue(mHeadsetService.isVirtualCallStarted());
+        verifyVirtualCallStartSequenceInvocations(connectedDevices);
+        // Virtual call should be preempted by telecom call
+        mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
+                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 the functionality of
+     * {@link BluetoothHeadset#startScoUsingVirtualVoiceCall()} and
+     * {@link BluetoothHeadset#stopScoUsingVirtualVoiceCall()}
+     *
+     * Virtual call should be rejected when there is a telecom call
+     */
+    @Test
+    public void testVirtualCall_rejectedWhenThereIsTelecomCall() throws RemoteException {
+        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()));
+        Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
+        BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2);
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        verify(mNativeInterface).setActiveDevice(activeDevice);
+        waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
+        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        // Reject virtual call setup if call state is not idle
+        when(mSystemInterface.isCallIdle()).thenReturn(false);
+        Assert.assertFalse(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall());
+        Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
+    }
+
+    /**
+     * Test the behavior when dialing outgoing call from the headset
+     */
+    @Test
+    public void testDialingOutCall_NormalDialingOut() throws RemoteException {
+        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()));
+        Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
+        BluetoothDevice activeDevice = connectedDevices.get(0);
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        verify(mNativeInterface).setActiveDevice(activeDevice);
+        waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
+        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        // Try dialing out from the a non active Headset
+        BluetoothDevice dialingOutDevice = connectedDevices.get(1);
+        HeadsetStackEvent dialingOutEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_DIAL_CALL, TEST_PHONE_NUMBER,
+                        dialingOutDevice);
+        Uri dialOutUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, TEST_PHONE_NUMBER, null);
+        Instrumentation.ActivityResult result =
+                new Instrumentation.ActivityResult(Activity.RESULT_OK, null);
+        Intents.intending(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED))
+                .respondWith(result);
+        mHeadsetService.messageFromNative(dialingOutEvent);
+        waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, dialingOutDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(
+                mHeadsetService.getStateMachinesThreadLooper());
+        Assert.assertTrue(mHeadsetService.hasDeviceInitiatedDialingOut());
+        // Make sure the correct intent is fired
+        Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED),
+                IntentMatchers.hasData(dialOutUri)), Intents.times(1));
+        // Further dial out attempt from same device will fail
+        mHeadsetService.messageFromNative(dialingOutEvent);
+        TestUtils.waitForLooperToFinishScheduledTask(
+                mHeadsetService.getStateMachinesThreadLooper());
+        verify(mNativeInterface).atResponseCode(dialingOutDevice,
+                HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        // Further dial out attempt from other device will fail
+        HeadsetStackEvent dialingOutEventOtherDevice =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_DIAL_CALL, TEST_PHONE_NUMBER,
+                        activeDevice);
+        mHeadsetService.messageFromNative(dialingOutEventOtherDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(
+                mHeadsetService.getStateMachinesThreadLooper());
+        verify(mNativeInterface).atResponseCode(activeDevice, HeadsetHalConstants.AT_RESPONSE_ERROR,
+                0);
+        TestUtils.waitForNoIntent(ASYNC_CALL_TIMEOUT_MILLIS, mActiveDeviceChangedQueue);
+        Assert.assertEquals(dialingOutDevice, mHeadsetServiceBinder.getActiveDevice());
+        // Make sure only one intent is fired
+        Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED),
+                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);
+        HeadsetCallState dialingCallState =
+                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
+                        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);
+        HeadsetCallState activeCallState =
+                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
+                        TEST_PHONE_NUMBER, 128);
+        verifyCallStateToNativeInvocation(activeCallState, connectedDevices);
+        Assert.assertFalse(mHeadsetService.hasDeviceInitiatedDialingOut());
+    }
+
+    /**
+     * Test the behavior when dialing outgoing call from the headset
+     */
+    @Test
+    public void testDialingOutCall_DialingOutPreemptVirtualCall() throws RemoteException {
+        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()));
+        Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
+        BluetoothDevice activeDevice = connectedDevices.get(0);
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        verify(mNativeInterface).setActiveDevice(activeDevice);
+        waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
+        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        // Start virtual call
+        Assert.assertTrue(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall());
+        Assert.assertTrue(mHeadsetService.isVirtualCallStarted());
+        verifyVirtualCallStartSequenceInvocations(connectedDevices);
+        // Try dialing out from the a non active Headset
+        BluetoothDevice dialingOutDevice = connectedDevices.get(1);
+        HeadsetStackEvent dialingOutEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_DIAL_CALL, TEST_PHONE_NUMBER,
+                        dialingOutDevice);
+        Uri dialOutUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, TEST_PHONE_NUMBER, null);
+        Instrumentation.ActivityResult result =
+                new Instrumentation.ActivityResult(Activity.RESULT_OK, null);
+        Intents.intending(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED))
+                .respondWith(result);
+        mHeadsetService.messageFromNative(dialingOutEvent);
+        waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, dialingOutDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(
+                mHeadsetService.getStateMachinesThreadLooper());
+        Assert.assertTrue(mHeadsetService.hasDeviceInitiatedDialingOut());
+        // Make sure the correct intent is fired
+        Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED),
+                IntentMatchers.hasData(dialOutUri)), Intents.times(1));
+        // Virtual call should be preempted by dialing out call
+        Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
+        verifyVirtualCallStopSequenceInvocations(connectedDevices);
+    }
+
+    /**
+     * Test to verify the following behavior regarding active HF initiated voice recognition
+     * in the successful scenario
+     *   1. HF device sends AT+BVRA=1
+     *   2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND}
+     *   3. AG call {@link BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} to indicate
+     *      that voice recognition has stopped
+     *   4. AG sends OK to HF
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_SingleHfInitiatedSuccess() {
+        // Connect HF
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(device);
+        // Make device active
+        Assert.assertTrue(mHeadsetService.setActiveDevice(device));
+        verify(mNativeInterface).setActiveDevice(device);
+        Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        // Start voice recognition
+        startVoiceRecognitionFromHf(device);
+    }
+
+    /**
+     * Test to verify the following behavior regarding active HF stop voice recognition
+     * in the successful scenario
+     *   1. HF device sends AT+BVRA=0
+     *   2. Let voice recognition app to stop
+     *   3. AG respond with OK
+     *   4. Disconnect audio
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_SingleHfStopSuccess() {
+        // Connect HF
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(device);
+        // Make device active
+        Assert.assertTrue(mHeadsetService.setActiveDevice(device));
+        verify(mNativeInterface).setActiveDevice(device);
+        Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        // Start voice recognition
+        startVoiceRecognitionFromHf(device);
+        // Stop voice recognition
+        HeadsetStackEvent stopVrEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
+                        HeadsetHalConstants.VR_STATE_STOPPED, device);
+        mHeadsetService.messageFromNative(stopVrEvent);
+        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).deactivateVoiceRecognition();
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).atResponseCode(device,
+                HeadsetHalConstants.AT_RESPONSE_OK, 0);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).disconnectAudio(device);
+        verifyNoMoreInteractions(mNativeInterface);
+    }
+
+    /**
+     * Test to verify the following behavior regarding active HF initiated voice recognition
+     * in the failed to activate scenario
+     *   1. HF device sends AT+BVRA=1
+     *   2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND}
+     *   3. Failed to activate voice recognition through intent
+     *   4. AG sends ERROR to HF
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_SingleHfInitiatedFailedToActivate() {
+        when(mSystemInterface.activateVoiceRecognition()).thenReturn(false);
+        // Connect HF
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(device);
+        // Make device active
+        Assert.assertTrue(mHeadsetService.setActiveDevice(device));
+        verify(mNativeInterface).setActiveDevice(device);
+        Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        // Start voice recognition
+        HeadsetStackEvent startVrEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
+                        HeadsetHalConstants.VR_STATE_STARTED, device);
+        mHeadsetService.messageFromNative(startVrEvent);
+        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition();
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(device,
+                HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        verifyNoMoreInteractions(mNativeInterface);
+        verifyZeroInteractions(mAudioManager);
+    }
+
+
+    /**
+     * Test to verify the following behavior regarding active HF initiated voice recognition
+     * in the timeout scenario
+     *   1. HF device sends AT+BVRA=1
+     *   2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND}
+     *   3. AG failed to get back to us on time
+     *   4. AG sends ERROR to HF
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_SingleHfInitiatedTimeout() {
+        // Connect HF
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(device);
+        // Make device active
+        Assert.assertTrue(mHeadsetService.setActiveDevice(device));
+        verify(mNativeInterface).setActiveDevice(device);
+        Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        // Start voice recognition
+        HeadsetStackEvent startVrEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
+                        HeadsetHalConstants.VR_STATE_STARTED, device);
+        mHeadsetService.messageFromNative(startVrEvent);
+        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition();
+        verify(mNativeInterface, timeout(START_VR_TIMEOUT_WAIT_MILLIS)).atResponseCode(device,
+                HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        verifyNoMoreInteractions(mNativeInterface);
+        verifyZeroInteractions(mAudioManager);
+    }
+
+    /**
+     * Test to verify the following behavior regarding AG initiated voice recognition
+     * in the successful scenario
+     *   1. AG starts voice recognition and notify the Bluetooth stack via
+     *      {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice
+     *      recognition has started
+     *   2. AG send +BVRA:1 to HF
+     *   3. AG start SCO connection if SCO has not been started
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_SingleAgInitiatedSuccess() {
+        // Connect HF
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(device);
+        // Make device active
+        Assert.assertTrue(mHeadsetService.setActiveDevice(device));
+        verify(mNativeInterface).setActiveDevice(device);
+        Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        // Start voice recognition
+        startVoiceRecognitionFromAg();
+    }
+
+    /**
+     * Test to verify the following behavior regarding AG initiated voice recognition
+     * in the successful scenario
+     *   1. AG starts voice recognition and notify the Bluetooth stack via
+     *      {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice
+     *      recognition has started, BluetoothDevice is null in this case
+     *   2. AG send +BVRA:1 to current active HF
+     *   3. AG start SCO connection if SCO has not been started
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_SingleAgInitiatedSuccessNullInput() {
+        // Connect HF
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(device);
+        // Make device active
+        Assert.assertTrue(mHeadsetService.setActiveDevice(device));
+        verify(mNativeInterface).setActiveDevice(device);
+        Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        // Start voice recognition on null argument should go to active device
+        Assert.assertTrue(mHeadsetService.startVoiceRecognition(null));
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).startVoiceRecognition(device);
+    }
+
+    /**
+     * Test to verify the following behavior regarding AG initiated voice recognition
+     * in the successful scenario
+     *   1. AG starts voice recognition and notify the Bluetooth stack via
+     *      {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice
+     *      recognition has started, BluetoothDevice is null and active device is null
+     *   2. The call should fail
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_SingleAgInitiatedFailNullActiveDevice() {
+        // Connect HF
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(device);
+        // Make device active
+        Assert.assertTrue(mHeadsetService.setActiveDevice(null));
+        // TODO(b/79760385): setActiveDevice(null) does not propagate to native layer
+        // verify(mNativeInterface).setActiveDevice(null);
+        Assert.assertNull(mHeadsetService.getActiveDevice());
+        // Start voice recognition on null argument should fail
+        Assert.assertFalse(mHeadsetService.startVoiceRecognition(null));
+    }
+
+    /**
+     * Test to verify the following behavior regarding AG stops voice recognition
+     * in the successful scenario
+     *   1. AG stops voice recognition and notify the Bluetooth stack via
+     *      {@link BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} to indicate that voice
+     *      recognition has stopped
+     *   2. AG send +BVRA:0 to HF
+     *   3. AG stop SCO connection
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_SingleAgStopSuccess() {
+        // Connect HF
+        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(device);
+        // Make device active
+        Assert.assertTrue(mHeadsetService.setActiveDevice(device));
+        verify(mNativeInterface).setActiveDevice(device);
+        Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        // Start voice recognition
+        startVoiceRecognitionFromAg();
+        // Stop voice recognition
+        Assert.assertTrue(mHeadsetService.stopVoiceRecognition(device));
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).stopVoiceRecognition(device);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).disconnectAudio(device);
+        verifyNoMoreInteractions(mNativeInterface);
+    }
+
+    /**
+     * Test to verify the following behavior regarding AG initiated voice recognition
+     * in the device not connected failure scenario
+     *   1. AG starts voice recognition and notify the Bluetooth stack via
+     *      {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice
+     *      recognition has started
+     *   2. Device is not connected, return false
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_SingleAgInitiatedDeviceNotConnected() {
+        // Start voice recognition
+        BluetoothDevice disconnectedDevice = TestUtils.getTestDevice(mAdapter, 0);
+        Assert.assertFalse(mHeadsetService.startVoiceRecognition(disconnectedDevice));
+        verifyNoMoreInteractions(mNativeInterface);
+        verifyZeroInteractions(mAudioManager);
+    }
+
+    /**
+     * Test to verify the following behavior regarding non active HF initiated voice recognition
+     * in the successful scenario
+     *   1. HF device sends AT+BVRA=1
+     *   2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND}
+     *   3. AG call {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate
+     *      that voice recognition has started
+     *   4. AG sends OK to HF
+     *   5. Suspend A2DP
+     *   6. Start SCO if SCO hasn't been started
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_MultiHfInitiatedSwitchActiveDeviceSuccess() {
+        // Connect two devices
+        BluetoothDevice deviceA = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(deviceA);
+        BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
+        connectTestDevice(deviceB);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+        // Set active device to device B
+        Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
+        verify(mNativeInterface).setActiveDevice(deviceB);
+        Assert.assertEquals(deviceB, mHeadsetService.getActiveDevice());
+        // Start voice recognition from non active device A
+        HeadsetStackEvent startVrEventA =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
+                        HeadsetHalConstants.VR_STATE_STARTED, deviceA);
+        mHeadsetService.messageFromNative(startVrEventA);
+        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition();
+        // Active device should have been swapped to device A
+        verify(mNativeInterface).setActiveDevice(deviceA);
+        Assert.assertEquals(deviceA, mHeadsetService.getActiveDevice());
+        // Start voice recognition from other device should fail
+        HeadsetStackEvent startVrEventB =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
+                        HeadsetHalConstants.VR_STATE_STARTED, deviceB);
+        mHeadsetService.messageFromNative(startVrEventB);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceB,
+                HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        // Reply to continue voice recognition
+        mHeadsetService.startVoiceRecognition(deviceA);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceA,
+                HeadsetHalConstants.AT_RESPONSE_OK, 0);
+        verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+                .setParameters("A2dpSuspended=true");
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(deviceA);
+        verifyNoMoreInteractions(mNativeInterface);
+    }
+
+    /**
+     * Test to verify the following behavior regarding non active HF initiated voice recognition
+     * in the successful scenario
+     *   1. HF device sends AT+BVRA=1
+     *   2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND}
+     *   3. AG call {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate
+     *      that voice recognition has started, but on a wrong HF
+     *   4. Headset service instead keep using the initiating HF
+     *   5. AG sends OK to HF
+     *   6. Suspend A2DP
+     *   7. Start SCO if SCO hasn't been started
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_MultiHfInitiatedSwitchActiveDeviceReplyWrongHfSuccess() {
+        // Connect two devices
+        BluetoothDevice deviceA = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(deviceA);
+        BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
+        connectTestDevice(deviceB);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+        // Set active device to device B
+        Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
+        verify(mNativeInterface).setActiveDevice(deviceB);
+        Assert.assertEquals(deviceB, mHeadsetService.getActiveDevice());
+        // Start voice recognition from non active device A
+        HeadsetStackEvent startVrEventA =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
+                        HeadsetHalConstants.VR_STATE_STARTED, deviceA);
+        mHeadsetService.messageFromNative(startVrEventA);
+        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition();
+        // Active device should have been swapped to device A
+        verify(mNativeInterface).setActiveDevice(deviceA);
+        Assert.assertEquals(deviceA, mHeadsetService.getActiveDevice());
+        // Start voice recognition from other device should fail
+        HeadsetStackEvent startVrEventB =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
+                        HeadsetHalConstants.VR_STATE_STARTED, deviceB);
+        mHeadsetService.messageFromNative(startVrEventB);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceB,
+                HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        // Reply to continue voice recognition on a wrong device
+        mHeadsetService.startVoiceRecognition(deviceB);
+        // We still continue on the initiating HF
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceA,
+                HeadsetHalConstants.AT_RESPONSE_OK, 0);
+        verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+                .setParameters("A2dpSuspended=true");
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(deviceA);
+        verifyNoMoreInteractions(mNativeInterface);
+    }
+
+
+    /**
+     * Test to verify the following behavior regarding AG initiated voice recognition
+     * in the successful scenario
+     *   1. AG starts voice recognition and notify the Bluetooth stack via
+     *      {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice
+     *      recognition has started
+     *   2. Suspend A2DP
+     *   3. AG send +BVRA:1 to HF
+     *   4. AG start SCO connection if SCO has not been started
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_MultiAgInitiatedSuccess() {
+        // Connect two devices
+        BluetoothDevice deviceA = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(deviceA);
+        BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
+        connectTestDevice(deviceB);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+        // Set active device to device B
+        Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
+        verify(mNativeInterface).setActiveDevice(deviceB);
+        Assert.assertEquals(deviceB, mHeadsetService.getActiveDevice());
+        // Start voice recognition
+        startVoiceRecognitionFromAg();
+        // Start voice recognition from other device should fail
+        HeadsetStackEvent startVrEventA =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
+                        HeadsetHalConstants.VR_STATE_STARTED, deviceA);
+        mHeadsetService.messageFromNative(startVrEventA);
+        // TODO(b/79660380): Workaround in case voice recognition was not terminated properly
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).stopVoiceRecognition(deviceB);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).disconnectAudio(deviceB);
+        // This request should still fail
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceA,
+                HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        verifyNoMoreInteractions(mNativeInterface);
+    }
+
+    /**
+     * Test to verify the following behavior regarding AG initiated voice recognition
+     * in the device not active failure scenario
+     *   1. AG starts voice recognition and notify the Bluetooth stack via
+     *      {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice
+     *      recognition has started
+     *   2. Device is not active, should do voice recognition on active device only
+     *
+     * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification
+     */
+    @Test
+    public void testVoiceRecognition_MultiAgInitiatedDeviceNotActive() {
+        // Connect two devices
+        BluetoothDevice deviceA = TestUtils.getTestDevice(mAdapter, 0);
+        connectTestDevice(deviceA);
+        BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
+        connectTestDevice(deviceB);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+        // Set active device to device B
+        Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
+        verify(mNativeInterface).setActiveDevice(deviceB);
+        Assert.assertEquals(deviceB, mHeadsetService.getActiveDevice());
+        // Start voice recognition should succeed
+        Assert.assertTrue(mHeadsetService.startVoiceRecognition(deviceA));
+        verify(mNativeInterface).setActiveDevice(deviceA);
+        Assert.assertEquals(deviceA, mHeadsetService.getActiveDevice());
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).startVoiceRecognition(deviceA);
+        verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+                .setParameters("A2dpSuspended=true");
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(deviceA);
+        waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, deviceA,
+                BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+        mHeadsetService.messageFromNative(
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED, deviceA));
+        waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, deviceA,
+                BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING);
+        verifyNoMoreInteractions(mNativeInterface);
+    }
+
+    private void startVoiceRecognitionFromHf(BluetoothDevice device) {
+        // Start voice recognition
+        HeadsetStackEvent startVrEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
+                        HeadsetHalConstants.VR_STATE_STARTED, device);
+        mHeadsetService.messageFromNative(startVrEvent);
+        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition();
+        Assert.assertTrue(mHeadsetService.startVoiceRecognition(device));
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(device,
+                HeadsetHalConstants.AT_RESPONSE_OK, 0);
+        verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+                .setParameters("A2dpSuspended=true");
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(device);
+        waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+        mHeadsetService.messageFromNative(
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED, device));
+        waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING);
+        verifyNoMoreInteractions(mNativeInterface);
+    }
+
+    private void startVoiceRecognitionFromAg() {
+        BluetoothDevice device = mHeadsetService.getActiveDevice();
+        Assert.assertNotNull(device);
+        Assert.assertTrue(mHeadsetService.startVoiceRecognition(device));
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).startVoiceRecognition(device);
+        verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+                .setParameters("A2dpSuspended=true");
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(device);
+        waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+        mHeadsetService.messageFromNative(
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED, device));
+        waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING);
+        verifyNoMoreInteractions(mNativeInterface);
+    }
+
+    private void connectTestDevice(BluetoothDevice device) {
+        // Make device bonded
+        mBondedDevices.add(device);
+        // Use connecting event to indicate that device is connecting
+        HeadsetStackEvent rfcommConnectedEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_CONNECTED, device);
+        mHeadsetService.messageFromNative(rfcommConnectedEvent);
+        verify(mObjectsFactory).makeStateMachine(device,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and
+        // 250ms for processing two messages should be way more than enough. Anything that breaks
+        // this indicate some breakage in other part of Android OS
+        waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mHeadsetService.getConnectionState(device));
+        Assert.assertEquals(Collections.singletonList(device),
+                mHeadsetService.getDevicesMatchingConnectionStates(
+                        new int[]{BluetoothProfile.STATE_CONNECTING}));
+        // Get feedback from native to put device into connected state
+        HeadsetStackEvent slcConnectedEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, device);
+        mHeadsetService.messageFromNative(slcConnectedEvent);
+        // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and
+        // 250ms for processing two messages should be way more than enough. Anything that breaks
+        // this indicate some breakage in other part of Android OS
+        waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mHeadsetService.getConnectionState(device));
+    }
+
+    private void waitAndVerifyConnectionStateIntent(int timeoutMs, BluetoothDevice device,
+            int newState, int prevState) {
+        Intent intent = TestUtils.waitForIntent(timeoutMs, mConnectionStateChangedQueue);
+        Assert.assertNotNull(intent);
+        HeadsetTestUtils.verifyConnectionStateBroadcast(device, newState, prevState, intent, false);
+    }
+
+    private void waitAndVerifyActiveDeviceChangedIntent(int timeoutMs, BluetoothDevice device) {
+        Intent intent = TestUtils.waitForIntent(timeoutMs, mActiveDeviceChangedQueue);
+        Assert.assertNotNull(intent);
+        HeadsetTestUtils.verifyActiveDeviceChangedBroadcast(device, intent, false);
+    }
+
+    private void waitAndVerifyAudioStateIntent(int timeoutMs, BluetoothDevice device, int newState,
+            int prevState) {
+        Intent intent = TestUtils.waitForIntent(timeoutMs, mAudioStateChangedQueue);
+        Assert.assertNotNull(intent);
+        HeadsetTestUtils.verifyAudioStateBroadcast(device, newState, prevState, intent);
+    }
+
+    /**
+     * Verify the series of invocations after
+     * {@link BluetoothHeadset#startScoUsingVirtualVoiceCall()}
+     *
+     * @param connectedDevices must be in the same sequence as
+     * {@link BluetoothHeadset#getConnectedDevices()}
+     */
+    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),
+                connectedDevices);
+        verifyCallStateToNativeInvocation(
+                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0),
+                connectedDevices);
+        verifyCallStateToNativeInvocation(
+                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),
+                connectedDevices);
+    }
+
+    private void verifyCallStateToNativeInvocation(HeadsetCallState headsetCallState,
+            List<BluetoothDevice> connectedDevices) {
+        for (BluetoothDevice device : connectedDevices) {
+            verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).phoneStateChange(device,
+                    headsetCallState);
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
new file mode 100644
index 0000000..e5395f2
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
@@ -0,0 +1,830 @@
+/*
+ * 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.hfp;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.IBluetoothHeadset;
+import android.content.Context;
+import android.media.AudioManager;
+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 com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+
+import org.hamcrest.Matchers;
+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;
+import org.mockito.Spy;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Set;
+
+/**
+ * Tests for {@link HeadsetService}
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class HeadsetServiceTest {
+    private static final int MAX_HEADSET_CONNECTIONS = 5;
+    private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.Handsfree};
+    private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250;
+    private static final String TEST_PHONE_NUMBER = "1234567890";
+
+    private Context mTargetContext;
+    private HeadsetService mHeadsetService;
+    private IBluetoothHeadset.Stub mHeadsetServiceBinder;
+    private BluetoothAdapter mAdapter;
+    private HeadsetNativeInterface mNativeInterface;
+    private BluetoothDevice mCurrentDevice;
+    private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>();
+
+    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    @Spy private HeadsetObjectsFactory mObjectsFactory = HeadsetObjectsFactory.getInstance();
+    @Mock private AdapterService mAdapterService;
+    @Mock private HeadsetSystemInterface mSystemInterface;
+    @Mock private AudioManager mAudioManager;
+    @Mock private HeadsetPhoneState mPhoneState;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when HeadsetService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        // We cannot mock HeadsetObjectsFactory.getInstance() with Mockito.
+        // Hence we need to use reflection to call a private method to
+        // initialize properly the HeadsetObjectsFactory.sInstance field.
+        Method method = HeadsetObjectsFactory.class.getDeclaredMethod("setInstanceForTesting",
+                HeadsetObjectsFactory.class);
+        method.setAccessible(true);
+        method.invoke(null, mObjectsFactory);
+        doReturn(true).when(mAdapterService).isEnabled();
+        doReturn(MAX_HEADSET_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices();
+        doReturn(new ParcelUuid[]{BluetoothUuid.Handsfree}).when(mAdapterService)
+                .getRemoteUuids(any(BluetoothDevice.class));
+        // This line must be called to make sure relevant objects are initialized properly
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        // Mock methods in AdapterService
+        doReturn(FAKE_HEADSET_UUID).when(mAdapterService)
+                .getRemoteUuids(any(BluetoothDevice.class));
+        doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
+                .getBondState(any(BluetoothDevice.class));
+        doAnswer(invocation -> {
+            Set<BluetoothDevice> keys = mStateMachines.keySet();
+            return keys.toArray(new BluetoothDevice[keys.size()]);
+        }).when(mAdapterService).getBondedDevices();
+        // Mock system interface
+        doNothing().when(mSystemInterface).init();
+        doNothing().when(mSystemInterface).stop();
+        when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
+        when(mSystemInterface.getAudioManager()).thenReturn(mAudioManager);
+        when(mSystemInterface.isCallIdle()).thenReturn(true);
+        // Mock methods in HeadsetNativeInterface
+        mNativeInterface = spy(HeadsetNativeInterface.getInstance());
+        doNothing().when(mNativeInterface).init(anyInt(), anyBoolean());
+        doNothing().when(mNativeInterface).cleanup();
+        doReturn(true).when(mNativeInterface).connectHfp(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectHfp(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).connectAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).setActiveDevice(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).sendBsir(any(BluetoothDevice.class), anyBoolean());
+        // Mock methods in HeadsetObjectsFactory
+        doAnswer(invocation -> {
+            Assert.assertNotNull(mCurrentDevice);
+            final HeadsetStateMachine stateMachine = mock(HeadsetStateMachine.class);
+            doReturn(BluetoothProfile.STATE_DISCONNECTED).when(stateMachine).getConnectionState();
+            doReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED).when(stateMachine).getAudioState();
+            mStateMachines.put(mCurrentDevice, stateMachine);
+            return stateMachine;
+        }).when(mObjectsFactory).makeStateMachine(any(), any(), any(), any(), any(), any());
+        doReturn(mSystemInterface).when(mObjectsFactory).makeSystemInterface(any());
+        doReturn(mNativeInterface).when(mObjectsFactory).getNativeInterface();
+        TestUtils.startService(mServiceRule, HeadsetService.class);
+        mHeadsetService = HeadsetService.getHeadsetService();
+        Assert.assertNotNull(mHeadsetService);
+        verify(mObjectsFactory).makeSystemInterface(mHeadsetService);
+        verify(mObjectsFactory).getNativeInterface();
+        mHeadsetServiceBinder = (IBluetoothHeadset.Stub) mHeadsetService.initBinder();
+        Assert.assertNotNull(mHeadsetServiceBinder);
+        mHeadsetServiceBinder.setForceScoAudio(true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, HeadsetService.class);
+        mHeadsetService = HeadsetService.getHeadsetService();
+        Assert.assertNull(mHeadsetService);
+        mStateMachines.clear();
+        mCurrentDevice = null;
+        Method method = HeadsetObjectsFactory.class.getDeclaredMethod("setInstanceForTesting",
+                HeadsetObjectsFactory.class);
+        method.setAccessible(true);
+        method.invoke(null, (HeadsetObjectsFactory) null);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    /**
+     * Test to verify that HeadsetService can be successfully started
+     */
+    @Test
+    public void testGetHeadsetService() {
+        Assert.assertEquals(mHeadsetService, HeadsetService.getHeadsetService());
+        // Verify default connection and audio states
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mHeadsetService.getConnectionState(mCurrentDevice));
+        Assert.assertEquals(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                mHeadsetService.getAudioState(mCurrentDevice));
+    }
+
+    /**
+     *  Test okToAcceptConnection method using various test cases
+     */
+    @Test
+    public void testOkToAcceptConnection() {
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        int badPriorityValue = 1024;
+        int badBondState = 42;
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
+                BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
+                BluetoothProfile.PRIORITY_OFF, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
+                BluetoothProfile.PRIORITY_ON, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
+                BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE, badPriorityValue,
+                false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
+                BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
+                BluetoothProfile.PRIORITY_OFF, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
+                BluetoothProfile.PRIORITY_ON, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
+                BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING, badPriorityValue,
+                false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
+                BluetoothProfile.PRIORITY_UNDEFINED, true);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
+                BluetoothProfile.PRIORITY_OFF, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
+                BluetoothProfile.PRIORITY_ON, true);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
+                BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED, badPriorityValue,
+                false);
+        testOkToAcceptConnectionCase(mCurrentDevice, badBondState,
+                BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, badBondState, BluetoothProfile.PRIORITY_OFF,
+                false);
+        testOkToAcceptConnectionCase(mCurrentDevice, badBondState, BluetoothProfile.PRIORITY_ON,
+                false);
+        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));
+    }
+
+    /**
+     * Test to verify that {@link HeadsetService#connect(BluetoothDevice)} returns true when the
+     * device was not connected and number of connected devices is less than
+     * {@link #MAX_HEADSET_CONNECTIONS}
+     */
+    @Test
+    public void testConnectDevice_connectDeviceBelowLimit() {
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+        verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT,
+                mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mHeadsetService.getConnectionState(mCurrentDevice));
+        when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mHeadsetService.getConnectionState(mCurrentDevice));
+        Assert.assertEquals(Collections.singletonList(mCurrentDevice),
+                mHeadsetService.getConnectedDevices());
+        // 2nd connection attempt will fail
+        Assert.assertFalse(mHeadsetService.connect(mCurrentDevice));
+        // Verify makeStateMachine is only called once
+        verify(mObjectsFactory).makeStateMachine(any(), any(), any(), any(), any(), any());
+        // Verify CONNECT is only sent once
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT),
+                any());
+    }
+
+    /**
+     * Test that {@link HeadsetService#messageFromNative(HeadsetStackEvent)} will send correct
+     * message to the underlying state machine
+     */
+    @Test
+    public void testMessageFromNative_deviceConnected() {
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        // Test connect from native
+        HeadsetStackEvent connectedEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_CONNECTED, mCurrentDevice);
+        mHeadsetService.messageFromNative(connectedEvent);
+        verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.STACK_EVENT,
+                connectedEvent);
+        when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mHeadsetService.getConnectionState(mCurrentDevice));
+        Assert.assertEquals(Collections.singletonList(mCurrentDevice),
+                mHeadsetService.getConnectedDevices());
+        // Test disconnect from native
+        HeadsetStackEvent disconnectEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mCurrentDevice);
+        mHeadsetService.messageFromNative(disconnectEvent);
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.STACK_EVENT,
+                disconnectEvent);
+        when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mHeadsetService.getConnectionState(mCurrentDevice));
+        Assert.assertEquals(Collections.EMPTY_LIST, mHeadsetService.getConnectedDevices());
+    }
+
+    /**
+     * Stack connection event to {@link HeadsetHalConstants#CONNECTION_STATE_CONNECTING} should
+     * create new state machine
+     */
+    @Test
+    public void testMessageFromNative_deviceConnectingUnknown() {
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        HeadsetStackEvent connectingEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_CONNECTING, mCurrentDevice);
+        mHeadsetService.messageFromNative(connectingEvent);
+        verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.STACK_EVENT,
+                connectingEvent);
+    }
+
+    /**
+     * Stack connection event to {@link HeadsetHalConstants#CONNECTION_STATE_DISCONNECTED} should
+     * crash by throwing {@link IllegalStateException} if the device is unknown
+     */
+    @Test
+    public void testMessageFromNative_deviceDisconnectedUnknown() {
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        HeadsetStackEvent connectingEvent =
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mCurrentDevice);
+        try {
+            mHeadsetService.messageFromNative(connectingEvent);
+            Assert.fail("Expect an IllegalStateException");
+        } catch (IllegalStateException exception) {
+            // Do nothing
+        }
+        verifyNoMoreInteractions(mObjectsFactory);
+    }
+
+    /**
+     * Test to verify that {@link HeadsetService#connect(BluetoothDevice)} fails after
+     * {@link #MAX_HEADSET_CONNECTIONS} connection requests
+     */
+    @Test
+    public void testConnectDevice_connectDeviceAboveLimit() {
+        ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
+            mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
+            Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+            verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                    mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService,
+                    mAdapterService, mNativeInterface, mSystemInterface);
+            verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class),
+                    eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService),
+                    eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface));
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT,
+                    mCurrentDevice);
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT),
+                    any(BluetoothDevice.class));
+            // Put device to connecting
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTING);
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+            // Put device to connected
+            connectedDevices.add(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTED);
+            Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                    mHeadsetService.getConnectionState(mCurrentDevice));
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+        }
+        // Connect the next device will fail
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, MAX_HEADSET_CONNECTIONS);
+        Assert.assertFalse(mHeadsetService.connect(mCurrentDevice));
+        // Though connection failed, a new state machine is still lazily created for the device
+        verify(mObjectsFactory, times(MAX_HEADSET_CONNECTIONS + 1)).makeStateMachine(
+                any(BluetoothDevice.class), eq(mHeadsetService.getStateMachinesThreadLooper()),
+                eq(mHeadsetService), eq(mAdapterService), eq(mNativeInterface),
+                eq(mSystemInterface));
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mHeadsetService.getConnectionState(mCurrentDevice));
+        Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                Matchers.containsInAnyOrder(connectedDevices.toArray()));
+    }
+
+    /**
+     * Test to verify that {@link HeadsetService#connectAudio(BluetoothDevice)} return true when
+     * the device is connected and audio is not connected and returns false when audio is already
+     * connecting
+     */
+    @Test
+    public void testConnectAudio_withOneDevice() {
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+        verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT,
+                mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                SystemClock.uptimeMillis());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mHeadsetService.getConnectionState(mCurrentDevice));
+        Assert.assertEquals(Collections.singletonList(mCurrentDevice),
+                mHeadsetService.getConnectedDevices());
+        mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);
+        // Test connect audio - set the device first as the active device
+        Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice));
+        Assert.assertTrue(mHeadsetService.connectAudio(mCurrentDevice));
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT_AUDIO,
+                mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn(
+                BluetoothHeadset.STATE_AUDIO_CONNECTING);
+        // 2nd connection attempt for the same device will succeed as well
+        Assert.assertTrue(mHeadsetService.connectAudio(mCurrentDevice));
+        // Verify CONNECT_AUDIO is only sent once
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(
+                eq(HeadsetStateMachine.CONNECT_AUDIO), any());
+        // Test disconnect audio
+        Assert.assertTrue(mHeadsetService.disconnectAudio(mCurrentDevice));
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO,
+                mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn(
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+        // Further disconnection requests will fail
+        Assert.assertFalse(mHeadsetService.disconnectAudio(mCurrentDevice));
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(
+                eq(HeadsetStateMachine.DISCONNECT_AUDIO), any(BluetoothDevice.class));
+    }
+
+    /**
+     * Test to verify that HFP audio connection can be initiated when multiple devices are connected
+     * and can be canceled or disconnected as well
+     */
+    @Test
+    public void testConnectAudio_withMultipleDevices() {
+        ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
+            mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
+            Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+            verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                    mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService,
+                    mAdapterService, mNativeInterface, mSystemInterface);
+            verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class),
+                    eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService),
+                    eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface));
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT,
+                    mCurrentDevice);
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT),
+                    any(BluetoothDevice.class));
+            // Put device to connecting
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTING);
+            mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                    BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+            // Put device to connected
+            connectedDevices.add(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTED);
+            when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                    SystemClock.uptimeMillis());
+            Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                    mHeadsetService.getConnectionState(mCurrentDevice));
+            mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                    BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED);
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+            // Try to connect audio
+            // Should fail
+            Assert.assertFalse(mHeadsetService.connectAudio(mCurrentDevice));
+            // Should succeed after setActiveDevice()
+            Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice));
+            Assert.assertTrue(mHeadsetService.connectAudio(mCurrentDevice));
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(
+                    HeadsetStateMachine.CONNECT_AUDIO, mCurrentDevice);
+            // Put device to audio connecting state
+            when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn(
+                    BluetoothHeadset.STATE_AUDIO_CONNECTING);
+            // 2nd connection attempt will also succeed
+            Assert.assertTrue(mHeadsetService.connectAudio(mCurrentDevice));
+            // Verify CONNECT_AUDIO is only sent once
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(
+                    eq(HeadsetStateMachine.CONNECT_AUDIO), any());
+            // Put device to audio connected state
+            when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn(
+                    BluetoothHeadset.STATE_AUDIO_CONNECTED);
+            // Disconnect audio
+            Assert.assertTrue(mHeadsetService.disconnectAudio(mCurrentDevice));
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(
+                    HeadsetStateMachine.DISCONNECT_AUDIO, mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn(
+                    BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+            // Further disconnection requests will fail
+            Assert.assertFalse(mHeadsetService.disconnectAudio(mCurrentDevice));
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(
+                    eq(HeadsetStateMachine.DISCONNECT_AUDIO), any(BluetoothDevice.class));
+        }
+    }
+
+    /**
+     * Verify that only one device can be in audio connecting or audio connected state, further
+     * attempt to call {@link HeadsetService#connectAudio(BluetoothDevice)} should fail by returning
+     * false
+     */
+    @Test
+    public void testConnectAudio_connectTwoAudioChannelsShouldFail() {
+        ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
+            mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
+            Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+            verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                    mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService,
+                    mAdapterService, mNativeInterface, mSystemInterface);
+            verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class),
+                    eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService),
+                    eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface));
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT,
+                    mCurrentDevice);
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT),
+                    any(BluetoothDevice.class));
+            // Put device to connecting
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTING);
+            mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                    BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+            // Put device to connected
+            connectedDevices.add(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTED);
+            when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                    SystemClock.uptimeMillis());
+            mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                    BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED);
+            Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                    mHeadsetService.getConnectionState(mCurrentDevice));
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+        }
+        if (MAX_HEADSET_CONNECTIONS >= 2) {
+            // Try to connect audio
+            BluetoothDevice firstDevice = connectedDevices.get(0);
+            BluetoothDevice secondDevice = connectedDevices.get(1);
+            // Set the first device as the active device
+            Assert.assertTrue(mHeadsetService.setActiveDevice(firstDevice));
+            Assert.assertTrue(mHeadsetService.connectAudio(firstDevice));
+            verify(mStateMachines.get(firstDevice)).sendMessage(HeadsetStateMachine.CONNECT_AUDIO,
+                    firstDevice);
+            // Put device to audio connecting state
+            when(mStateMachines.get(firstDevice).getAudioState()).thenReturn(
+                    BluetoothHeadset.STATE_AUDIO_CONNECTING);
+            // 2nd connection attempt will succeed for the same device
+            Assert.assertTrue(mHeadsetService.connectAudio(firstDevice));
+            // Connect to 2nd device will fail
+            Assert.assertFalse(mHeadsetService.connectAudio(secondDevice));
+            verify(mStateMachines.get(secondDevice), never()).sendMessage(
+                    HeadsetStateMachine.CONNECT_AUDIO, secondDevice);
+            // Put device to audio connected state
+            when(mStateMachines.get(firstDevice).getAudioState()).thenReturn(
+                    BluetoothHeadset.STATE_AUDIO_CONNECTED);
+            // Connect to 2nd device will fail
+            Assert.assertFalse(mHeadsetService.connectAudio(secondDevice));
+            verify(mStateMachines.get(secondDevice), never()).sendMessage(
+                    HeadsetStateMachine.CONNECT_AUDIO, secondDevice);
+        }
+    }
+
+    /**
+     * Verify that {@link HeadsetService#connectAudio()} will connect to first connected/connecting
+     * device
+     */
+    @Test
+    public void testConnectAudio_firstConnectedAudioDevice() {
+        ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        doAnswer(invocation -> {
+            BluetoothDevice[] devicesArray = new BluetoothDevice[connectedDevices.size()];
+            return connectedDevices.toArray(devicesArray);
+        }).when(mAdapterService).getBondedDevices();
+        for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
+            mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
+            Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+            verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                    mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService,
+                    mAdapterService, mNativeInterface, mSystemInterface);
+            verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class),
+                    eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService),
+                    eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface));
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT,
+                    mCurrentDevice);
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT),
+                    any(BluetoothDevice.class));
+            // Put device to connecting
+            when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                    SystemClock.uptimeMillis());
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTING);
+            mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                    BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+            // Put device to connected
+            connectedDevices.add(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTED);
+            when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                    SystemClock.uptimeMillis());
+            Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                    mHeadsetService.getConnectionState(mCurrentDevice));
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+            mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                    BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED);
+        }
+        // Try to connect audio
+        BluetoothDevice firstDevice = connectedDevices.get(0);
+        Assert.assertTrue(mHeadsetService.setActiveDevice(firstDevice));
+        Assert.assertTrue(mHeadsetService.connectAudio());
+        verify(mStateMachines.get(firstDevice)).sendMessage(HeadsetStateMachine.CONNECT_AUDIO,
+                firstDevice);
+    }
+
+    /**
+     * Test to verify that {@link HeadsetService#connectAudio(BluetoothDevice)} fails if device
+     * was never connected
+     */
+    @Test
+    public void testConnectAudio_deviceNeverConnected() {
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        Assert.assertFalse(mHeadsetService.connectAudio(mCurrentDevice));
+    }
+
+    /**
+     * Test to verify that {@link HeadsetService#connectAudio(BluetoothDevice)} fails if device
+     * is disconnected
+     */
+    @Test
+    public void testConnectAudio_deviceDisconnected() {
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        HeadsetCallState headsetCallState =
+                new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
+                        TEST_PHONE_NUMBER, 128);
+        Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+        verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT,
+                mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+        // Put device in disconnected state
+        when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mHeadsetService.getConnectionState(mCurrentDevice));
+        Assert.assertEquals(Collections.EMPTY_LIST, mHeadsetService.getConnectedDevices());
+        mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
+        // connectAudio should fail
+        Assert.assertFalse(mHeadsetService.connectAudio(mCurrentDevice));
+        verify(mStateMachines.get(mCurrentDevice), never()).sendMessage(
+                eq(HeadsetStateMachine.CONNECT_AUDIO), any());
+    }
+
+    /**
+     * Verifies that phone state change will trigger a system-wide saving of call state even when
+     * no device is connected
+     *
+     * @throws RemoteException if binder call fails
+     */
+    @Test
+    public void testPhoneStateChange_noDeviceSaveState() throws RemoteException {
+        HeadsetCallState headsetCallState =
+                new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
+                        TEST_PHONE_NUMBER, 128);
+        mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
+                headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
+                headsetCallState.mType);
+        HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState,
+                ASYNC_CALL_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Verifies that phone state change will trigger a system-wide saving of call state and send
+     * state change to connected devices
+     *
+     * @throws RemoteException if binder call fails
+     */
+    @Test
+    public void testPhoneStateChange_oneDeviceSaveState() throws RemoteException {
+        HeadsetCallState headsetCallState =
+                new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
+                        TEST_PHONE_NUMBER, 128);
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        // Connect one device
+        Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+        verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService,
+                mNativeInterface, mSystemInterface);
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT,
+                mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+        // Put device to connecting
+        when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                SystemClock.uptimeMillis());
+        when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                BluetoothProfile.STATE_CONNECTING);
+        mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
+        Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                Matchers.containsInAnyOrder(connectedDevices.toArray()));
+        // Put device to connected
+        connectedDevices.add(mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                SystemClock.uptimeMillis());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mHeadsetService.getConnectionState(mCurrentDevice));
+        Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                Matchers.containsInAnyOrder(connectedDevices.toArray()));
+        mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED);
+        // Change phone state
+        mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
+                headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
+                headsetCallState.mType);
+        // Make sure we notify device about this change
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(
+                HeadsetStateMachine.CALL_STATE_CHANGED, headsetCallState);
+        // Make sure state is updated once in phone state holder
+        HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState,
+                ASYNC_CALL_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Verifies that phone state change will trigger a system-wide saving of call state and send
+     * state change to connected devices
+     *
+     * @throws RemoteException if binder call fails
+     */
+    @Test
+    public void testPhoneStateChange_multipleDevicesSaveState() throws RemoteException {
+        HeadsetCallState headsetCallState =
+                new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
+                        TEST_PHONE_NUMBER, 128);
+        final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
+        for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
+            mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
+            Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+            verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
+                    mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService,
+                    mAdapterService, mNativeInterface, mSystemInterface);
+            verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class),
+                    eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService),
+                    eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface));
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT,
+                    mCurrentDevice);
+            verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT),
+                    any(BluetoothDevice.class));
+            // Put device to connecting
+            when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                    SystemClock.uptimeMillis());
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTING);
+            mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                    BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+            // Put device to connected
+            connectedDevices.add(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+            when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                    BluetoothProfile.STATE_CONNECTED);
+            when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                    SystemClock.uptimeMillis());
+            Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                    mHeadsetService.getConnectionState(mCurrentDevice));
+            Assert.assertThat(mHeadsetService.getConnectedDevices(),
+                    Matchers.containsInAnyOrder(connectedDevices.toArray()));
+            mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                    BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED);
+        }
+        // Change phone state
+        mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
+                headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
+                headsetCallState.mType);
+        // Make sure we notify devices about this change
+        for (BluetoothDevice device : connectedDevices) {
+            verify(mStateMachines.get(device)).sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
+                    headsetCallState);
+        }
+        // Make sure state is updated once in phone state holder
+        HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState,
+                ASYNC_CALL_TIMEOUT_MILLIS);
+    }
+
+    /*
+     *  Helper function to test okToAcceptConnection() method
+     *
+     *  @param device test device
+     *  @param bondState bond state value, could be invalid
+     *  @param priority value, could be invalid, coudl be invalid
+     *  @param expected expected result from okToAcceptConnection()
+     */
+    private void testOkToAcceptConnectionCase(BluetoothDevice device, int bondState, int priority,
+            boolean expected) {
+        doReturn(bondState).when(mAdapterService).getBondState(device);
+        Assert.assertTrue(mHeadsetService.setPriority(device, 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
new file mode 100644
index 0000000..80192a6
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -0,0 +1,1050 @@
+/*
+ * Copyright 2017 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.hfp;
+
+import static org.mockito.Mockito.*;
+
+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.content.ServiceConnection;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.net.Uri;
+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 com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+
+import org.hamcrest.core.IsInstanceOf;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+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;
+
+/**
+ * Tests for {@link HeadsetStateMachine}
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class HeadsetStateMachineTest {
+    private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000;
+    private static final int CONNECT_TIMEOUT_TEST_WAIT_MILLIS = CONNECT_TIMEOUT_TEST_MILLIS * 3 / 2;
+    private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250;
+    private static final String TEST_PHONE_NUMBER = "1234567890";
+    private Context mTargetContext;
+    private BluetoothAdapter mAdapter;
+    private HandlerThread mHandlerThread;
+    private HeadsetStateMachine mHeadsetStateMachine;
+    private BluetoothDevice mTestDevice;
+    private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private HeadsetService mHeadsetService;
+    @Mock private HeadsetSystemInterface mSystemInterface;
+    @Mock private AudioManager mAudioManager;
+    @Mock private HeadsetPhoneState mPhoneState;
+    private MockContentResolver mMockContentResolver;
+    private HeadsetNativeInterface mNativeInterface;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when HeadsetService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp));
+        // Setup mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        // Stub system interface
+        when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
+        when(mSystemInterface.getAudioManager()).thenReturn(mAudioManager);
+        // 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");
+        // Spy on native interface
+        mNativeInterface = spy(HeadsetNativeInterface.getInstance());
+        doNothing().when(mNativeInterface).init(anyInt(), anyBoolean());
+        doReturn(true).when(mNativeInterface).connectHfp(mTestDevice);
+        doReturn(true).when(mNativeInterface).disconnectHfp(mTestDevice);
+        doReturn(true).when(mNativeInterface).connectAudio(mTestDevice);
+        doReturn(true).when(mNativeInterface).disconnectAudio(mTestDevice);
+        // Stub headset service
+        mMockContentResolver = new MockContentResolver();
+        when(mHeadsetService.getContentResolver()).thenReturn(mMockContentResolver);
+        doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
+                .getBondState(any(BluetoothDevice.class));
+        when(mHeadsetService.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
+                .thenReturn(true);
+        when(mHeadsetService.getResources()).thenReturn(
+                InstrumentationRegistry.getTargetContext().getResources());
+        when(mHeadsetService.getPackageManager()).thenReturn(
+                InstrumentationRegistry.getContext().getPackageManager());
+        when(mHeadsetService.getPriority(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetService.getForceScoAudio()).thenReturn(true);
+        when(mHeadsetService.okToAcceptConnection(any(BluetoothDevice.class))).thenReturn(true);
+        when(mHeadsetService.isScoAcceptable(any(BluetoothDevice.class))).thenReturn(true);
+        // Setup thread and looper
+        mHandlerThread = new HandlerThread("HeadsetStateMachineTestHandlerThread");
+        mHandlerThread.start();
+        // Modify CONNECT timeout to a smaller value for test only
+        HeadsetStateMachine.sConnectTimeoutMs = CONNECT_TIMEOUT_TEST_MILLIS;
+        mHeadsetStateMachine = HeadsetObjectsFactory.getInstance()
+                .makeStateMachine(mTestDevice, mHandlerThread.getLooper(), mHeadsetService,
+                        mAdapterService, mNativeInterface, mSystemInterface);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp)) {
+            return;
+        }
+        HeadsetObjectsFactory.getInstance().destroyStateMachine(mHeadsetStateMachine);
+        mHandlerThread.quit();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    /**
+     * Test that default state is Disconnected
+     */
+    @Test
+    public void testDefaultDisconnectedState() {
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mHeadsetStateMachine.getConnectionState());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test that state is Connected after calling setUpConnectedState()
+     */
+    @Test
+    public void testSetupConnectedState() {
+        setUpConnectedState();
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mHeadsetStateMachine.getConnectionState());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+    }
+
+    /**
+     * Test state transition from Disconnected to Connecting state via CONNECT message
+     */
+    @Test
+    public void testStateTransition_DisconnectedToConnecting_Connect() {
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.CONNECT, mTestDevice);
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
+    }
+
+    /**
+     * Test state transition from Disconnected to Connecting state via StackEvent.CONNECTED message
+     */
+    @Test
+    public void testStateTransition_DisconnectedToConnecting_StackConnected() {
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_CONNECTED, mTestDevice));
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
+    }
+
+    /**
+     * Test state transition from Disconnected to Connecting state via StackEvent.CONNECTING message
+     */
+    @Test
+    public void testStateTransition_DisconnectedToConnecting_StackConnecting() {
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_CONNECTING, mTestDevice));
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
+    }
+
+    /**
+     * Test state transition from Connecting to Disconnected state via StackEvent.DISCONNECTED
+     * message
+     */
+    @Test
+    public void testStateTransition_ConnectingToDisconnected_StackDisconnected() {
+        int numBroadcastsSent = setUpConnectingState();
+        // Indicate disconnecting to test state machine, which should do nothing
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING, mTestDevice));
+        // Should do nothing new
+        verify(mHeadsetService,
+                after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                any(Intent.class), any(UserHandle.class), anyString());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
+
+        // Indicate connection failed to test state machine
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
+
+        numBroadcastsSent++;
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test state transition from Connecting to Disconnected state via CONNECT_TIMEOUT message
+     */
+    @Test
+    public void testStateTransition_ConnectingToDisconnected_Timeout() {
+        int numBroadcastsSent = setUpConnectingState();
+        // Let the connection timeout
+        numBroadcastsSent++;
+        verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times(
+                numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(),
+                eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test state transition from Connecting to Connected state via StackEvent.SLC_CONNECTED message
+     */
+    @Test
+    public void testStateTransition_ConnectingToConnected_StackSlcConnected() {
+        int numBroadcastsSent = setUpConnectingState();
+        // Indicate connecting to test state machine
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_CONNECTING, mTestDevice));
+        // Should do nothing
+        verify(mHeadsetService,
+                after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                any(Intent.class), any(UserHandle.class), anyString());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
+
+        // Indicate RFCOMM connection is successful to test state machine
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_CONNECTED, mTestDevice));
+        // Should do nothing
+        verify(mHeadsetService,
+                after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                any(Intent.class), any(UserHandle.class), anyString());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
+
+        // Indicate SLC connection is successful to test state machine
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+    }
+
+    /**
+     * Test state transition from Disconnecting to Disconnected state via StackEvent.DISCONNECTED
+     * message
+     */
+    @Test
+    public void testStateTransition_DisconnectingToDisconnected_StackDisconnected() {
+        int numBroadcastsSent = setUpDisconnectingState();
+        // Send StackEvent.DISCONNECTED message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test state transition from Disconnecting to Disconnected state via CONNECT_TIMEOUT
+     * message
+     */
+    @Test
+    public void testStateTransition_DisconnectingToDisconnected_Timeout() {
+        int numBroadcastsSent = setUpDisconnectingState();
+        // Let the connection timeout
+        numBroadcastsSent++;
+        verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times(
+                numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(),
+                eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test state transition from Disconnecting to Connected state via StackEvent.SLC_CONNECTED
+     * message
+     */
+    @Test
+    public void testStateTransition_DisconnectingToConnected_StackSlcCconnected() {
+        int numBroadcastsSent = setUpDisconnectingState();
+        // Send StackEvent.SLC_CONNECTED message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+    }
+
+    /**
+     * Test state transition from Connected to Disconnecting state via DISCONNECT message
+     */
+    @Test
+    public void testStateTransition_ConnectedToDisconnecting_Disconnect() {
+        int numBroadcastsSent = setUpConnectedState();
+        // Send DISCONNECT message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, mTestDevice);
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class));
+    }
+
+    /**
+     * Test state transition from Connected to Disconnecting state via StackEvent.DISCONNECTING
+     * message
+     */
+    @Test
+    public void testStateTransition_ConnectedToDisconnecting_StackDisconnecting() {
+        int numBroadcastsSent = setUpConnectedState();
+        // Send StackEvent.DISCONNECTING message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class));
+    }
+
+    /**
+     * Test state transition from Connected to Disconnected state via StackEvent.DISCONNECTED
+     * message
+     */
+    @Test
+    public void testStateTransition_ConnectedToDisconnected_StackDisconnected() {
+        int numBroadcastsSent = setUpConnectedState();
+        // Send StackEvent.DISCONNECTED message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test state transition from Connected to AudioConnecting state via CONNECT_AUDIO message
+     */
+    @Test
+    public void testStateTransition_ConnectedToAudioConnecting_ConnectAudio() {
+        int numBroadcastsSent = setUpConnectedState();
+        // Send CONNECT_AUDIO message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, mTestDevice);
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioConnecting.class));
+    }
+
+    /**
+     * Test state transition from Connected to AudioConnecting state via
+     * StackEvent.AUDIO_CONNECTING message
+     */
+    @Test
+    public void testStateTransition_ConnectedToAudioConnecting_StackAudioConnecting() {
+        int numBroadcastsSent = setUpConnectedState();
+        // Send StackEvent.AUDIO_CONNECTING message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTING, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioConnecting.class));
+    }
+
+    /**
+     * Test state transition from Connected to AudioOn state via StackEvent.AUDIO_CONNECTED message
+     */
+    @Test
+    public void testStateTransition_ConnectedToAudioOn_StackAudioConnected() {
+        int numBroadcastsSent = setUpConnectedState();
+        // Send StackEvent.AUDIO_CONNECTED message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class));
+    }
+
+    /**
+     * Test state transition from AudioConnecting to Connected state via CONNECT_TIMEOUT message
+     */
+    @Test
+    public void testStateTransition_AudioConnectingToConnected_Timeout() {
+        int numBroadcastsSent = setUpAudioConnectingState();
+        // Wait for connection to timeout
+        numBroadcastsSent++;
+        verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times(
+                numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(),
+                eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+    }
+
+    /**
+     * Test state transition from AudioConnecting to Connected state via
+     * StackEvent.AUDIO_DISCONNECTED message
+     */
+    @Test
+    public void testStateTransition_AudioConnectingToConnected_StackAudioDisconnected() {
+        int numBroadcastsSent = setUpAudioConnectingState();
+        // Send StackEvent.AUDIO_DISCONNECTED message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+    }
+
+    /**
+     * Test state transition from AudioConnecting to Disconnected state via
+     * StackEvent.DISCONNECTED message
+     */
+    @Test
+    public void testStateTransition_AudioConnectingToDisconnected_StackDisconnected() {
+        int numBroadcastsSent = setUpAudioConnectingState();
+        // Send StackEvent.DISCONNECTED message
+        numBroadcastsSent += 2;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1));
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test state transition from AudioConnecting to Disconnecting state via
+     * StackEvent.DISCONNECTING message
+     */
+    @Test
+    public void testStateTransition_AudioConnectingToDisconnecting_StackDisconnecting() {
+        int numBroadcastsSent = setUpAudioConnectingState();
+        // Send StackEvent.DISCONNECTED message
+        numBroadcastsSent += 2;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1));
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class));
+    }
+
+    /**
+     * Test state transition from AudioConnecting to AudioOn state via
+     * StackEvent.AUDIO_CONNECTED message
+     */
+    @Test
+    public void testStateTransition_AudioConnectingToAudioOn_StackAudioConnected() {
+        int numBroadcastsSent = setUpAudioConnectingState();
+        // Send StackEvent.AUDIO_DISCONNECTED message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class));
+    }
+
+    /**
+     * Test state transition from AudioOn to AudioDisconnecting state via
+     * StackEvent.AUDIO_DISCONNECTING message
+     */
+    @Test
+    public void testStateTransition_AudioOnToAudioDisconnecting_StackAudioDisconnecting() {
+        int numBroadcastsSent = setUpAudioOnState();
+        // Send StackEvent.AUDIO_DISCONNECTING message
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_DISCONNECTING, mTestDevice));
+        verify(mHeadsetService,
+                after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                any(Intent.class), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class));
+    }
+
+    /**
+     * Test state transition from AudioOn to AudioDisconnecting state via
+     * DISCONNECT_AUDIO message
+     */
+    @Test
+    public void testStateTransition_AudioOnToAudioDisconnecting_DisconnectAudio() {
+        int numBroadcastsSent = setUpAudioOnState();
+        // Send DISCONNECT_AUDIO message
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, mTestDevice);
+        // Should not sent any broadcast due to lack of AUDIO_DISCONNECTING intent value
+        verify(mHeadsetService,
+                after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                any(Intent.class), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class));
+    }
+
+    /**
+     * Test state transition from AudioOn to AudioDisconnecting state via
+     * Stack.AUDIO_DISCONNECTED message
+     */
+    @Test
+    public void testStateTransition_AudioOnToConnected_StackAudioDisconnected() {
+        int numBroadcastsSent = setUpAudioOnState();
+        // Send DISCONNECT_AUDIO message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+    }
+
+    /**
+     * Test state transition from AudioOn to Disconnected state via
+     * Stack.DISCONNECTED message
+     */
+    @Test
+    public void testStateTransition_AudioOnToDisconnected_StackDisconnected() {
+        int numBroadcastsSent = setUpAudioOnState();
+        // Send StackEvent.DISCONNECTED message
+        numBroadcastsSent += 2;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1));
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test state transition from AudioOn to Disconnecting state via
+     * Stack.DISCONNECTING message
+     */
+    @Test
+    public void testStateTransition_AudioOnToDisconnecting_StackDisconnecting() {
+        int numBroadcastsSent = setUpAudioOnState();
+        // Send StackEvent.DISCONNECTING message
+        numBroadcastsSent += 2;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1));
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class));
+    }
+
+    /**
+     * Test state transition from AudioDisconnecting to Connected state via
+     * CONNECT_TIMEOUT message
+     */
+    @Test
+    public void testStateTransition_AudioDisconnectingToConnected_Timeout() {
+        int numBroadcastsSent = setUpAudioDisconnectingState();
+        // Wait for connection to timeout
+        numBroadcastsSent++;
+        verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times(
+                numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(),
+                eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+    }
+
+    /**
+     * Test state transition from AudioDisconnecting to Connected state via
+     * Stack.AUDIO_DISCONNECTED message
+     */
+    @Test
+    public void testStateTransition_AudioDisconnectingToConnected_StackAudioDisconnected() {
+        int numBroadcastsSent = setUpAudioDisconnectingState();
+        // Send Stack.AUDIO_DISCONNECTED message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+    }
+
+    /**
+     * Test state transition from AudioDisconnecting to AudioOn state via
+     * Stack.AUDIO_CONNECTED message
+     */
+    @Test
+    public void testStateTransition_AudioDisconnectingToAudioOn_StackAudioConnected() {
+        int numBroadcastsSent = setUpAudioDisconnectingState();
+        // Send Stack.AUDIO_CONNECTED message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class));
+    }
+
+    /**
+     * Test state transition from AudioDisconnecting to Disconnecting state via
+     * Stack.DISCONNECTING message
+     */
+    @Test
+    public void testStateTransition_AudioDisconnectingToDisconnecting_StackDisconnecting() {
+        int numBroadcastsSent = setUpAudioDisconnectingState();
+        // Send StackEvent.DISCONNECTING message
+        numBroadcastsSent += 2;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1));
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class));
+    }
+
+    /**
+     * Test state transition from AudioDisconnecting to Disconnecting state via
+     * Stack.DISCONNECTED message
+     */
+    @Test
+    public void testStateTransition_AudioDisconnectingToDisconnected_StackDisconnected() {
+        int numBroadcastsSent = setUpAudioDisconnectingState();
+        // Send StackEvent.DISCONNECTED message
+        numBroadcastsSent += 2;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 1));
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnected.class));
+    }
+
+    /**
+     * A test to verify that we correctly subscribe to phone state updates for service and signal
+     * strength information and further updates via AT+BIA command results in update
+     */
+    @Test
+    public void testAtBiaEvent_initialSubscriptionWithUpdates() {
+        setUpConnectedState();
+        verify(mPhoneState).listenForPhoneState(mTestDevice, PhoneStateListener.LISTEN_SERVICE_STATE
+                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA,
+                        new HeadsetAgIndicatorEnableState(true, true, false, false), mTestDevice));
+        verify(mPhoneState, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).listenForPhoneState(mTestDevice,
+                PhoneStateListener.LISTEN_SERVICE_STATE);
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA,
+                        new HeadsetAgIndicatorEnableState(false, true, true, false), mTestDevice));
+        verify(mPhoneState, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).listenForPhoneState(mTestDevice,
+                PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA,
+                        new HeadsetAgIndicatorEnableState(false, true, false, false), mTestDevice));
+        verify(mPhoneState, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).listenForPhoneState(mTestDevice,
+                PhoneStateListener.LISTEN_NONE);
+    }
+
+    /**
+     * A test to verify that we correctly handles key pressed event from a HSP headset
+     */
+    @Test
+    public void testKeyPressedEventWhenIdleAndAudioOff_dialCall() {
+        setUpConnectedState();
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.getCount()).thenReturn(1);
+        when(cursor.moveToNext()).thenReturn(true);
+        int magicNumber = 42;
+        when(cursor.getColumnIndexOrThrow(CallLog.Calls.NUMBER)).thenReturn(magicNumber);
+        when(cursor.getString(magicNumber)).thenReturn(TEST_PHONE_NUMBER);
+        MockContentProvider mockContentProvider = new MockContentProvider() {
+            @Override
+            public Cursor query(Uri uri, String[] projection, String selection,
+                    String[] selectionArgs, String sortOrder) {
+                if (uri == null || !uri.equals(CallLog.Calls.CONTENT_URI)) {
+                    return null;
+                }
+                if (projection == null || (projection.length == 0) || !projection[0].equals(
+                        CallLog.Calls.NUMBER)) {
+                    return null;
+                }
+                if (selection == null || !selection.equals(
+                        CallLog.Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE)) {
+                    return null;
+                }
+                if (selectionArgs != null) {
+                    return null;
+                }
+                if (sortOrder == null || !sortOrder.equals(
+                        CallLog.Calls.DEFAULT_SORT_ORDER + " LIMIT 1")) {
+                    return null;
+                }
+                return cursor;
+            }
+        };
+        mMockContentResolver.addProvider(CallLog.AUTHORITY, mockContentProvider);
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).dialOutgoingCall(mTestDevice,
+                TEST_PHONE_NUMBER);
+    }
+
+    /**
+     * A test to verify that we correctly handles key pressed event from a HSP headset
+     */
+    @Test
+    public void testKeyPressedEventDuringRinging_answerCall() {
+        setUpConnectedState();
+        when(mSystemInterface.isRinging()).thenReturn(true);
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
+        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).answerCall(mTestDevice);
+    }
+
+    /**
+     * A test to verify that we correctly handles key pressed event from a HSP headset
+     */
+    @Test
+    public void testKeyPressedEventInCallButAudioOff_setActiveDevice() {
+        setUpConnectedState();
+        when(mSystemInterface.isInCall()).thenReturn(true);
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setActiveDevice(mTestDevice);
+    }
+
+    /**
+     * A test to verify that we correctly handles key pressed event from a HSP headset
+     */
+    @Test
+    public void testKeyPressedEventInCallAndAudioOn_hangupCall() {
+        setUpAudioOnState();
+        when(mSystemInterface.isInCall()).thenReturn(true);
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
+        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).hangupCall(mTestDevice);
+    }
+
+    /**
+     * A test to verify that we correctly handles key pressed event from a HSP headset
+     */
+    @Test
+    public void testKeyPressedEventWhenIdleAndAudioOn_disconnectAudio() {
+        setUpAudioOnState();
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).disconnectAudio(mTestDevice);
+    }
+
+    /**
+     * Setup Connecting State
+     * @return number of times mHeadsetService.sendBroadcastAsUser() has been invoked
+     */
+    private int setUpConnectingState() {
+        // Put test state machine in connecting state
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.CONNECT, mTestDevice);
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
+        return 1;
+    }
+
+    /**
+     * Setup Connected State
+     * @return number of times mHeadsetService.sendBroadcastAsUser() has been invoked
+     */
+    private int setUpConnectedState() {
+        // Put test state machine into connected state
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_CONNECTED, mTestDevice));
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
+                        HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, mTestDevice));
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+        return 2;
+    }
+
+    private int setUpAudioConnectingState() {
+        int numBroadcastsSent = setUpConnectedState();
+        // Send CONNECT_AUDIO
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, mTestDevice);
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioConnecting.class));
+        return numBroadcastsSent;
+    }
+
+    private int setUpAudioOnState() {
+        int numBroadcastsSent = setUpAudioConnectingState();
+        // Send StackEvent.AUDIO_DISCONNECTED message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED, mTestDevice));
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+                BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class));
+        return numBroadcastsSent;
+    }
+
+    private int setUpAudioDisconnectingState() {
+        int numBroadcastsSent = setUpAudioOnState();
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, mTestDevice);
+        // No new broadcast due to lack of AUDIO_DISCONNECTING intent variable
+        verify(mHeadsetService,
+                after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                any(Intent.class), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class));
+        return numBroadcastsSent;
+    }
+
+    private int setUpDisconnectingState() {
+        int numBroadcastsSent = setUpConnectedState();
+        // Send DISCONNECT message
+        numBroadcastsSent++;
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, mTestDevice);
+        verify(mHeadsetService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+        HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
+                BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED,
+                mIntentArgument.getValue());
+        Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class));
+        return numBroadcastsSent;
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetTestUtils.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetTestUtils.java
new file mode 100644
index 0000000..4279afb
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetTestUtils.java
@@ -0,0 +1,125 @@
+/*
+ * 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.hfp;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+
+import org.junit.Assert;
+
+/**
+ * Helper functions for HFP related tests
+ */
+public class HeadsetTestUtils {
+
+    /**
+     * Verify the content of a {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED} intent
+     *
+     * @param device Bluetooth device
+     * @param toState value of {@link BluetoothProfile#EXTRA_STATE}
+     * @param fromState value of {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}
+     * @param intent a {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED} intent
+     */
+    public static void verifyAudioStateBroadcast(BluetoothDevice device, int toState, int fromState,
+            Intent intent) {
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED, intent.getAction());
+        Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(toState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertEquals(fromState,
+                intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1));
+    }
+
+    /**
+     * Verify the content of a {@link BluetoothHeadset#ACTION_CONNECTION_STATE_CHANGED} intent
+     *
+     * @param device Bluetooth device
+     * @param toState value of {@link BluetoothProfile#EXTRA_STATE}
+     * @param fromState value of {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}
+     * @param intent a {@link BluetoothHeadset#ACTION_CONNECTION_STATE_CHANGED} intent
+     * @param checkFlag whether intent flag should be verified, normally this can only be done at
+     *                  the sender end
+     */
+    public static void verifyConnectionStateBroadcast(BluetoothDevice device, int toState,
+            int fromState, Intent intent, boolean checkFlag) {
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, intent.getAction());
+        if (checkFlag) {
+            Assert.assertEquals(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, intent.getFlags());
+        }
+        Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(toState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertEquals(fromState,
+                intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1));
+    }
+
+    /**
+     * Verify the content of a {@link BluetoothHeadset#ACTION_CONNECTION_STATE_CHANGED} intent
+     * and its flag, normally used at sender end
+     *
+     * @param device Bluetooth device
+     * @param toState value of {@link BluetoothProfile#EXTRA_STATE}
+     * @param fromState value of {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}
+     * @param intent a {@link BluetoothHeadset#ACTION_CONNECTION_STATE_CHANGED} intent
+     */
+    public static void verifyConnectionStateBroadcast(BluetoothDevice device, int toState,
+            int fromState, Intent intent) {
+        verifyConnectionStateBroadcast(device, toState, fromState, intent, true);
+    }
+
+    /**
+     * Verify the content of a {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} intent
+     * and its flag, normally used at sender end
+     *
+     * @param device intended active Bluetooth device
+     * @param intent a {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} intent
+     * @param checkFlag whether intent flag should be verified, normally this can only be done at
+     *                  the sender end
+     */
+    public static void verifyActiveDeviceChangedBroadcast(BluetoothDevice device, Intent intent,
+            boolean checkFlag) {
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED, intent.getAction());
+        Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        if (checkFlag) {
+            Assert.assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, intent.getFlags());
+        }
+    }
+
+    /**
+     * Helper function to check if {@link HeadsetPhoneState} is set to correct values indicated in
+     * {@param headsetCallState}
+     *
+     * @param headsetPhoneState a mocked {@link HeadsetPhoneState}
+     * @param headsetCallState intended headset call state
+     * @param timeoutMs timeout for this check in asynchronous test conditions
+     */
+    public static void verifyPhoneStateChangeSetters(HeadsetPhoneState headsetPhoneState,
+            HeadsetCallState headsetCallState, int timeoutMs) {
+        verify(headsetPhoneState, timeout(timeoutMs).times(1)).setNumActiveCall(
+                headsetCallState.mNumActive);
+        verify(headsetPhoneState, timeout(timeoutMs).times(1)).setNumHeldCall(
+                headsetCallState.mNumHeld);
+        verify(headsetPhoneState, timeout(timeoutMs).times(1)).setCallState(
+                headsetCallState.mCallState);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
new file mode 100644
index 0000000..5826415
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.hfpclient;
+
+import android.bluetooth.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 HeadsetClientServiceTest {
+    private HeadsetClientService 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 HeadsetClientService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hfpclient));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, HeadsetClientService.class);
+        // At this point the service should have started so check NOT null
+        mService = HeadsetClientService.getHeadsetClientService();
+        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_hfpclient)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, HeadsetClientService.class);
+        mService = HeadsetClientService.getHeadsetClientService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(HeadsetClientService.getHeadsetClientService());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
new file mode 100644
index 0000000..334597b
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
@@ -0,0 +1,332 @@
+package com.android.bluetooth.hfpclient;
+
+import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHeadsetClientCall;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+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 com.android.bluetooth.R;
+
+import org.hamcrest.core.AllOf;
+import org.hamcrest.core.IsInstanceOf;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+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 org.mockito.hamcrest.MockitoHamcrest;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class HeadsetClientStateMachineTest {
+    private BluetoothAdapter mAdapter;
+    private HandlerThread mHandlerThread;
+    private HeadsetClientStateMachine mHeadsetClientStateMachine;
+    private BluetoothDevice mTestDevice;
+    private Context mTargetContext;
+
+    @Mock
+    private Resources mMockHfpResources;
+    @Mock
+    private HeadsetClientService mHeadsetClientService;
+    @Mock
+    private AudioManager mAudioManager;
+
+    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
+            * 3 / 2;
+
+    @Before
+    public void setUp() {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when HeadsetClientService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hfpclient));
+        // Setup mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        // Set a valid volume
+        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(
+                mAudioManager);
+        when(mHeadsetClientService.getResources()).thenReturn(mMockHfpResources);
+        when(mMockHfpResources.getBoolean(R.bool.hfp_clcc_poll_during_call)).thenReturn(true);
+
+        // 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");
+
+        // Setup thread and looper
+        mHandlerThread = new HandlerThread("HeadsetClientStateMachineTestHandlerThread");
+        mHandlerThread.start();
+        // Manage looper execution in main test thread explicitly to guarantee timing consistency
+        mHeadsetClientStateMachine =
+                new HeadsetClientStateMachine(mHeadsetClientService, mHandlerThread.getLooper());
+        mHeadsetClientStateMachine.start();
+    }
+
+    @After
+    public void tearDown() {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hfpclient)) {
+            return;
+        }
+        mHeadsetClientStateMachine.doQuit();
+        mHandlerThread.quit();
+    }
+
+    /**
+     * Test that default state is disconnected
+     */
+    @SmallTest
+    @Test
+    public void testDefaultDisconnectedState() {
+        Assert.assertEquals(mHeadsetClientStateMachine.getConnectionState(null),
+                BluetoothProfile.STATE_DISCONNECTED);
+    }
+
+    /**
+     * Test that an incoming connection with low priority is rejected
+     */
+    @MediumTest
+    @Test
+    public void testIncomingPriorityReject() {
+        // Return false for priority.
+        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.PRIORITY_OFF);
+
+        // Inject an event for when 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);
+
+        // Verify that only DISCONNECTED -> DISCONNECTED broadcast is fired
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(MockitoHamcrest
+                .argThat(
+                AllOf.allOf(IntentMatchers.hasAction(
+                        BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED),
+                        IntentMatchers.hasExtra(BluetoothProfile.EXTRA_STATE,
+                                BluetoothProfile.STATE_DISCONNECTED),
+                        IntentMatchers.hasExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                                BluetoothProfile.STATE_DISCONNECTED))), anyString());
+        // Check we are in disconnected state still.
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test that an incoming connection with high priority is accepted
+     */
+    @MediumTest
+    @Test
+    public void testIncomingPriorityAccept() {
+        // Return true for priority.
+        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+
+        // Inject an event for when 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);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument1
+                .capture(),
+                anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check we are in connecting state now.
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
+
+        // Send a message to 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);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(2)).sendBroadcast(
+                intentArgument2.capture(), anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        // Check we are in connecting state now.
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+    }
+
+    /**
+     * Test that an incoming connection that times out
+     */
+    @MediumTest
+    @Test
+    public void testIncomingTimeout() {
+        // Return true for priority.
+        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+
+        // Inject an event for when 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);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument1
+                .capture(),
+                anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check we are in connecting state now.
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetClientService,
+                timeout(HeadsetClientStateMachine.CONNECTING_TIMEOUT_MS * 2).times(
+                        2)).sendBroadcast(intentArgument2.capture(), anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check we are in connecting state now.
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
+    }
+
+    /**
+     * Test that In Band Ringtone information is relayed from phone.
+     */
+    @LargeTest
+    @Test
+    public void testInBandRingtone() {
+        // Return true for priority.
+        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.PRIORITY_ON);
+
+        Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing());
+
+        // Inject an event for when 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);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument
+                .capture(),
+                anyString());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Send a message to 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);
+
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(2)).sendBroadcast(
+                intentArgument.capture(),
+                anyString());
+
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
+        event.valueInt = 0;
+        event.device = mTestDevice;
+
+        // Enable In Band Ring and verify state gets propagated.
+        StackEvent eventInBandRing = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
+        eventInBandRing.valueInt = 1;
+        eventInBandRing.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast(
+                intentArgument.capture(),
+                anyString());
+        Assert.assertEquals(1,
+                intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
+                        -1));
+        Assert.assertEquals(true, mHeadsetClientStateMachine.getInBandRing());
+
+
+        // Simulate a new incoming phone call
+        StackEvent eventCallStatusUpdated = new StackEvent(StackEvent.EVENT_TYPE_CLIP);
+        mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventCallStatusUpdated);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast(
+                intentArgument.capture(),
+                anyString());
+
+        // Provide information about the new call
+        StackEvent eventIncomingCall = new StackEvent(StackEvent.EVENT_TYPE_CURRENT_CALLS);
+        eventIncomingCall.valueInt = 1; //index
+        eventIncomingCall.valueInt2 = 1; //direction
+        eventIncomingCall.valueInt3 = 4; //state
+        eventIncomingCall.valueInt4 = 0; //multi party
+        eventIncomingCall.valueString = "5551212"; //phone number
+        eventIncomingCall.device = mTestDevice;
+
+        mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventIncomingCall);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast(
+                intentArgument.capture(),
+                anyString());
+
+
+        // Signal that the complete list of calls was received.
+        StackEvent eventCommandStatus = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
+        eventCommandStatus.valueInt = AT_OK;
+        mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventCommandStatus);
+        verify(mHeadsetClientService, timeout(QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS).times(4))
+                .sendBroadcast(
+                intentArgument.capture(),
+                anyString());
+
+        // Verify that the new call is being registered with the inBandRing flag set.
+        Assert.assertEquals(true,
+                ((BluetoothHeadsetClientCall) intentArgument.getValue().getParcelableExtra(
+                        BluetoothHeadsetClient.EXTRA_CALL)).isInBandRing());
+
+        // Disable In Band Ring and verify state gets propagated.
+        eventInBandRing.valueInt = 0;
+        mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(5)).sendBroadcast(
+                intentArgument.capture(),
+                anyString());
+        Assert.assertEquals(0,
+                intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
+                        -1));
+        Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing());
+
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
new file mode 100644
index 0000000..618d2b4
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright 2017 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.hid;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidDevice;
+import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHidDeviceCallback;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+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 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;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class HidDeviceTest {
+    private static final int TIMEOUT_MS = 1000;    // 1s
+    private static final byte[] SAMPLE_HID_REPORT = new byte[]{0x01, 0x00, 0x02};
+    private static final byte SAMPLE_REPORT_ID = 0x00;
+    private static final byte SAMPLE_REPORT_TYPE = 0x00;
+    private static final byte SAMPLE_REPORT_ERROR = 0x02;
+    private static final byte SAMPLE_BUFFER_SIZE = 100;
+
+    private static final int CALLBACK_APP_REGISTERED = 0;
+    private static final int CALLBACK_APP_UNREGISTERED = 1;
+    private static final int CALLBACK_ON_GET_REPORT = 2;
+    private static final int CALLBACK_ON_SET_REPORT = 3;
+    private static final int CALLBACK_ON_SET_PROTOCOL = 4;
+    private static final int CALLBACK_ON_INTR_DATA = 5;
+    private static final int CALLBACK_ON_VIRTUAL_UNPLUG = 6;
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private HidDeviceNativeInterface mHidDeviceNativeInterface;
+
+    private BluetoothAdapter mAdapter;
+    private BluetoothDevice mTestDevice;
+    private HidDeviceService mHidDeviceService;
+    private Context mTargetContext;
+    private BluetoothHidDeviceAppSdpSettings mSettings;
+    private BroadcastReceiver mConnectionStateChangedReceiver;
+    private final BlockingQueue<Intent> mConnectionStateChangedQueue = new LinkedBlockingQueue<>();
+    private final BlockingQueue<Integer> mCallbackQueue = new LinkedBlockingQueue<>();
+
+    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    private static void setHidDeviceNativeInterfaceInstance(HidDeviceNativeInterface instance)
+            throws Exception {
+        Method method = HidDeviceNativeInterface.class.getDeclaredMethod("setInstance",
+                HidDeviceNativeInterface.class);
+        method.setAccessible(true);
+        method.invoke(null, instance);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when HidDeviceService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hid_device));
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        setHidDeviceNativeInterfaceInstance(mHidDeviceNativeInterface);
+        // This line must be called to make sure relevant objects are initialized properly
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        // Get a device for testing
+        mTestDevice = mAdapter.getRemoteDevice("10:11:12:13:14:15");
+
+        TestUtils.startService(mServiceRule, HidDeviceService.class);
+        mHidDeviceService = HidDeviceService.getHidDeviceService();
+        Assert.assertNotNull(mHidDeviceService);
+
+        // Force unregister app first
+        mHidDeviceService.unregisterApp();
+
+        Field field = HidDeviceService.class.getDeclaredField("mHidDeviceNativeInterface");
+        field.setAccessible(true);
+        HidDeviceNativeInterface nativeInterface =
+                (HidDeviceNativeInterface) field.get(mHidDeviceService);
+        Assert.assertEquals(nativeInterface, mHidDeviceNativeInterface);
+
+        // Dummy SDP settings
+        mSettings = new BluetoothHidDeviceAppSdpSettings("Unit test", "test", "Android",
+                BluetoothHidDevice.SUBCLASS1_COMBO, new byte[]{});
+
+        // Set up the Connection State Changed receiver
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
+        mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
+        mTargetContext.registerReceiver(mConnectionStateChangedReceiver, filter);
+        reset(mHidDeviceNativeInterface, mAdapterService);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hid_device)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, HidDeviceService.class);
+        mHidDeviceService = HidDeviceService.getHidDeviceService();
+        Assert.assertNull(mHidDeviceService);
+        mTargetContext.unregisterReceiver(mConnectionStateChangedReceiver);
+        mConnectionStateChangedQueue.clear();
+        mCallbackQueue.clear();
+        setHidDeviceNativeInterfaceInstance(null);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    private class ConnectionStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            try {
+                mConnectionStateChangedQueue.put(intent);
+            } catch (InterruptedException e) {
+                Assert.fail("Cannot add Intent to the queue");
+            }
+        }
+    }
+
+    private Intent waitForIntent(int timeoutMs, BlockingQueue<Intent> queue) {
+        try {
+            Intent intent = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
+            Assert.assertNotNull(intent);
+            return intent;
+        } catch (InterruptedException e) {
+            Assert.fail("Cannot obtain an Intent from the queue");
+        }
+        return null;
+    }
+
+    private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, int newState,
+            int prevState) {
+        Intent intent = waitForIntent(timeoutMs, mConnectionStateChangedQueue);
+        Assert.assertNotNull(intent);
+        Assert.assertEquals(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED, intent.getAction());
+        Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertEquals(prevState,
+                intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1));
+    }
+
+    private void verifyCallback(int timeoutMs, int callbackType, BlockingQueue<Integer> queue) {
+        try {
+            Integer lastCallback = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
+            Assert.assertNotNull(lastCallback);
+            int lastCallbackType = lastCallback;
+            Assert.assertEquals(callbackType, lastCallbackType);
+        } catch (InterruptedException e) {
+            Assert.fail("Cannot obtain a callback from the queue");
+        }
+    }
+
+    class BluetoothHidDeviceCallbackTestHelper extends IBluetoothHidDeviceCallback.Stub {
+        public void onAppStatusChanged(BluetoothDevice device, boolean registered) {
+            try {
+                if (registered) {
+                    mCallbackQueue.put(CALLBACK_APP_REGISTERED);
+                } else {
+                    mCallbackQueue.put(CALLBACK_APP_UNREGISTERED);
+                }
+            } catch (InterruptedException e) {
+                Assert.fail("Cannot add Intent to the queue");
+            }
+        }
+
+        public void onConnectionStateChanged(BluetoothDevice device, int state) {
+
+        }
+
+        public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
+            try {
+                mCallbackQueue.put(CALLBACK_ON_GET_REPORT);
+            } catch (InterruptedException e) {
+                Assert.fail("Cannot add Intent to the queue");
+            }
+        }
+
+        public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+            try {
+                mCallbackQueue.put(CALLBACK_ON_SET_REPORT);
+            } catch (InterruptedException e) {
+                Assert.fail("Cannot add Intent to the queue");
+            }
+        }
+
+        public void onSetProtocol(BluetoothDevice device, byte protocol) {
+            try {
+                mCallbackQueue.put(CALLBACK_ON_SET_PROTOCOL);
+            } catch (InterruptedException e) {
+                Assert.fail("Cannot add Intent to the queue");
+            }
+        }
+
+        public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+            try {
+                mCallbackQueue.put(CALLBACK_ON_INTR_DATA);
+            } catch (InterruptedException e) {
+                Assert.fail("Cannot add Intent to the queue");
+            }
+        }
+
+        public void onVirtualCableUnplug(BluetoothDevice device) {
+            try {
+                mCallbackQueue.put(CALLBACK_ON_VIRTUAL_UNPLUG);
+            } catch (InterruptedException e) {
+                Assert.fail("Cannot add Intent to the queue");
+            }
+        }
+    }
+
+    /**
+     * Test getting HidDeviceService: getHidDeviceService().
+     */
+    @Test
+    public void testGetHidDeviceService() {
+        Assert.assertEquals(mHidDeviceService, HidDeviceService.getHidDeviceService());
+    }
+
+    /**
+     * Test the logic in registerApp and unregisterApp. Should get a callback
+     * onApplicationStateChangedFromNative.
+     */
+    @Test
+    public void testRegistration() throws Exception {
+        doReturn(true).when(mHidDeviceNativeInterface)
+                .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
+                        isNull(), isNull());
+
+        verify(mHidDeviceNativeInterface, never()).registerApp(anyString(), anyString(),
+                anyString(), anyByte(), any(byte[].class), isNull(), isNull());
+
+        // Register app
+        BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
+        Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
+
+        verify(mHidDeviceNativeInterface).registerApp(anyString(), anyString(), anyString(),
+                anyByte(), any(byte[].class), isNull(), isNull());
+
+        // App registered
+        mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
+        verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
+
+        // Unregister app
+        doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
+        Assert.assertEquals(true, mHidDeviceService.unregisterApp());
+
+        verify(mHidDeviceNativeInterface).unregisterApp();
+
+        mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, false);
+        verifyCallback(TIMEOUT_MS, CALLBACK_APP_UNREGISTERED, mCallbackQueue);
+
+    }
+
+    /**
+     * Test the logic in sendReport(). This should fail when the app is not registered.
+     */
+    @Test
+    public void testSendReport() throws Exception {
+        doReturn(true).when(mHidDeviceNativeInterface).sendReport(anyInt(), any(byte[].class));
+        // sendReport() should fail without app registered
+        Assert.assertEquals(false,
+                mHidDeviceService.sendReport(mTestDevice, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT));
+
+        // Register app
+        doReturn(true).when(mHidDeviceNativeInterface)
+                .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
+                        isNull(), isNull());
+        BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
+        Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
+
+        // App registered
+        mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
+
+        // Wait for the app registration callback to complete and verify it
+        verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
+
+        // sendReport() should work when app is registered
+        Assert.assertEquals(true,
+                mHidDeviceService.sendReport(mTestDevice, SAMPLE_REPORT_ID, SAMPLE_HID_REPORT));
+
+        verify(mHidDeviceNativeInterface).sendReport(eq((int) SAMPLE_REPORT_ID),
+                eq(SAMPLE_HID_REPORT));
+
+        // Unregister app
+        doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
+        Assert.assertEquals(true, mHidDeviceService.unregisterApp());
+    }
+
+    /**
+     * Test the logic in replyReport(). This should fail when the app is not registered.
+     */
+    @Test
+    public void testReplyReport() throws Exception {
+        doReturn(true).when(mHidDeviceNativeInterface)
+                .replyReport(anyByte(), anyByte(), any(byte[].class));
+        // replyReport() should fail without app registered
+        Assert.assertEquals(false,
+                mHidDeviceService.replyReport(mTestDevice, SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID,
+                        SAMPLE_HID_REPORT));
+
+        // Register app
+        doReturn(true).when(mHidDeviceNativeInterface)
+                .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
+                        isNull(), isNull());
+        BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
+        Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
+
+        // App registered
+        mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
+
+        // Wait for the app registration callback to complete and verify it
+        verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
+
+        // replyReport() should work when app is registered
+        Assert.assertEquals(true,
+                mHidDeviceService.replyReport(mTestDevice, SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID,
+                        SAMPLE_HID_REPORT));
+
+        verify(mHidDeviceNativeInterface).replyReport(eq(SAMPLE_REPORT_TYPE), eq(SAMPLE_REPORT_ID),
+                eq(SAMPLE_HID_REPORT));
+
+        // Unregister app
+        doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
+        Assert.assertEquals(true, mHidDeviceService.unregisterApp());
+    }
+
+    /**
+     * Test the logic in reportError(). This should fail when the app is not registered.
+     */
+    @Test
+    public void testReportError() throws Exception {
+        doReturn(true).when(mHidDeviceNativeInterface).reportError(anyByte());
+        // reportError() should fail without app registered
+        Assert.assertEquals(false, mHidDeviceService.reportError(mTestDevice, SAMPLE_REPORT_ERROR));
+
+        // Register app
+        doReturn(true).when(mHidDeviceNativeInterface)
+                .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
+                        isNull(), isNull());
+        BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
+        Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
+
+        // App registered
+        mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
+
+        // Wait for the app registration callback to complete and verify it
+        verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
+
+        // reportError() should work when app is registered
+        Assert.assertEquals(true, mHidDeviceService.reportError(mTestDevice, SAMPLE_REPORT_ERROR));
+
+        verify(mHidDeviceNativeInterface).reportError(eq(SAMPLE_REPORT_ERROR));
+
+        // Unregister app
+        doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
+        Assert.assertEquals(true, mHidDeviceService.unregisterApp());
+    }
+
+    /**
+     * Test that an outgoing connection/disconnection succeeds
+     */
+    @Test
+    public void testOutgoingConnectDisconnectSuccess() {
+        doReturn(true).when(mHidDeviceNativeInterface).connect(any(BluetoothDevice.class));
+        doReturn(true).when(mHidDeviceNativeInterface).disconnect();
+
+        // Register app
+        doReturn(true).when(mHidDeviceNativeInterface)
+                .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
+                        isNull(), isNull());
+        mHidDeviceService.registerApp(mSettings, null, null, null);
+
+        // App registered
+        mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
+
+        // Send a connect request
+        Assert.assertTrue("Connect failed", mHidDeviceService.connect(mTestDevice));
+
+        mHidDeviceService.onConnectStateChangedFromNative(mTestDevice,
+                HidDeviceService.HAL_CONN_STATE_CONNECTING);
+        // Verify the connection state broadcast
+        verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mHidDeviceService.getConnectionState(mTestDevice));
+
+        mHidDeviceService.onConnectStateChangedFromNative(mTestDevice,
+                HidDeviceService.HAL_CONN_STATE_CONNECTED);
+        // Verify the connection state broadcast
+        verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mHidDeviceService.getConnectionState(mTestDevice));
+
+        // Verify the list of connected devices
+        Assert.assertTrue(mHidDeviceService.getDevicesMatchingConnectionStates(
+                new int[]{BluetoothProfile.STATE_CONNECTED}).contains(mTestDevice));
+
+        // Send a disconnect request
+        Assert.assertTrue("Disconnect failed", mHidDeviceService.disconnect(mTestDevice));
+
+        mHidDeviceService.onConnectStateChangedFromNative(mTestDevice,
+                HidDeviceService.HAL_CONN_STATE_DISCONNECTING);
+        // Verify the connection state broadcast
+        verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_CONNECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
+                mHidDeviceService.getConnectionState(mTestDevice));
+
+        mHidDeviceService.onConnectStateChangedFromNative(mTestDevice,
+                HidDeviceService.HAL_CONN_STATE_DISCONNECTED);
+        // Verify the connection state broadcast
+        verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_DISCONNECTING);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mHidDeviceService.getConnectionState(mTestDevice));
+
+        // Verify the list of connected devices
+        Assert.assertFalse(mHidDeviceService.getDevicesMatchingConnectionStates(
+                new int[]{BluetoothProfile.STATE_CONNECTED}).contains(mTestDevice));
+
+        // Unregister app
+        doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
+        Assert.assertEquals(true, mHidDeviceService.unregisterApp());
+    }
+
+    /**
+     * Test the logic in callback functions from native stack: onGetReport, onSetReport,
+     * onSetProtocol, onInterruptData, onVirtualCableUnplug. The HID Device server should send the
+     * callback to the user app.
+     */
+    @Test
+    public void testCallbacks() {
+        doReturn(true).when(mHidDeviceNativeInterface)
+                .registerApp(anyString(), anyString(), anyString(), anyByte(), any(byte[].class),
+                        isNull(), isNull());
+
+        verify(mHidDeviceNativeInterface, never()).registerApp(anyString(), anyString(),
+                anyString(), anyByte(), any(byte[].class), isNull(), isNull());
+
+        // Register app
+        BluetoothHidDeviceCallbackTestHelper helper = new BluetoothHidDeviceCallbackTestHelper();
+        Assert.assertTrue(mHidDeviceService.registerApp(mSettings, null, null, helper));
+
+        verify(mHidDeviceNativeInterface).registerApp(anyString(), anyString(), anyString(),
+                anyByte(), any(byte[].class), isNull(), isNull());
+
+        // App registered
+        mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, true);
+        verifyCallback(TIMEOUT_MS, CALLBACK_APP_REGISTERED, mCallbackQueue);
+
+        // Received callback: onGetReport
+        mHidDeviceService.onGetReportFromNative(SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID,
+                SAMPLE_BUFFER_SIZE);
+        verifyCallback(TIMEOUT_MS, CALLBACK_ON_GET_REPORT, mCallbackQueue);
+
+        // Received callback: onSetReport
+        mHidDeviceService.onSetReportFromNative(SAMPLE_REPORT_TYPE, SAMPLE_REPORT_ID,
+                SAMPLE_HID_REPORT);
+        verifyCallback(TIMEOUT_MS, CALLBACK_ON_SET_REPORT, mCallbackQueue);
+
+        // Received callback: onSetProtocol
+        mHidDeviceService.onSetProtocolFromNative(BluetoothHidDevice.PROTOCOL_BOOT_MODE);
+        verifyCallback(TIMEOUT_MS, CALLBACK_ON_SET_PROTOCOL, mCallbackQueue);
+
+        // Received callback: onInterruptData
+        mHidDeviceService.onInterruptDataFromNative(SAMPLE_REPORT_ID, SAMPLE_HID_REPORT);
+        verifyCallback(TIMEOUT_MS, CALLBACK_ON_INTR_DATA, mCallbackQueue);
+
+        // Received callback: onVirtualCableUnplug
+        mHidDeviceService.onVirtualCableUnplugFromNative();
+        verifyCallback(TIMEOUT_MS, CALLBACK_ON_VIRTUAL_UNPLUG, mCallbackQueue);
+
+        // Unregister app
+        doReturn(true).when(mHidDeviceNativeInterface).unregisterApp();
+        Assert.assertEquals(true, mHidDeviceService.unregisterApp());
+
+        verify(mHidDeviceNativeInterface).unregisterApp();
+
+        mHidDeviceService.onApplicationStateChangedFromNative(mTestDevice, false);
+        verifyCallback(TIMEOUT_MS, CALLBACK_APP_UNREGISTERED, mCallbackQueue);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
new file mode 100644
index 0000000..939e068
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.hid;
+
+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 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 HidHostServiceTest {
+    private HidHostService mService = null;
+    private BluetoothAdapter mAdapter = null;
+    private BluetoothDevice mTestDevice;
+    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 HidHostService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_hid_host));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, HidHostService.class);
+        mService = HidHostService.getHidHostService();
+        Assert.assertNotNull(mService);
+        // Try getting the Bluetooth adapter
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        Assert.assertNotNull(mAdapter);
+
+        // Get a device for testing
+        mTestDevice = TestUtils.getTestDevice(mAdapter, 0);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hid_host)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, HidHostService.class);
+        mService = HidHostService.getHidHostService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(HidHostService.getHidHostService());
+    }
+
+    /**
+     *  Test okToConnect method using various test cases
+     */
+    @Test
+    public void testOkToConnect() {
+        int badPriorityValue = 1024;
+        int badBondState = 42;
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_NONE, badPriorityValue, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDING, badPriorityValue, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+        testOkToConnectCase(mTestDevice,
+                BluetoothDevice.BOND_BONDED, badPriorityValue, false);
+        testOkToConnectCase(mTestDevice,
+                badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testOkToConnectCase(mTestDevice,
+                badBondState, BluetoothProfile.PRIORITY_OFF, false);
+        testOkToConnectCase(mTestDevice,
+                badBondState, BluetoothProfile.PRIORITY_ON, false);
+        testOkToConnectCase(mTestDevice,
+                badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+        testOkToConnectCase(mTestDevice,
+                badBondState, badPriorityValue, false);
+        // Restore prirority to undefined for this test device
+        Assert.assertTrue(mService.setPriority(
+                mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED));
+    }
+
+    /**
+     * Helper function to test okToConnect() method.
+     *
+     * @param device test device
+     * @param bondState bond state value, could be invalid
+     * @param priority value, could be invalid
+     * @param expected expected result from okToConnect()
+     */
+    private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
+            boolean expected) {
+        doReturn(bondState).when(mAdapterService).getBondState(device);
+        Assert.assertTrue(mService.setPriority(device, priority));
+
+        // Test when the AdapterService is in non-quiet mode.
+        doReturn(false).when(mAdapterService).isQuietModeEnabled();
+        Assert.assertEquals(expected, mService.okToConnect(device));
+
+        // Test when the AdapterService is in quiet mode.
+        doReturn(true).when(mAdapterService).isQuietModeEnabled();
+        Assert.assertEquals(false, mService.okToConnect(device));
+    }
+
+}
diff --git a/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java b/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
new file mode 100644
index 0000000..47b72d6
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 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.map;
+
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+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 com.android.bluetooth.R;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapContentObserverTest {
+    private Context mTargetContext;
+
+    class ExceptionTestProvider extends MockContentProvider {
+        public ExceptionTestProvider(Context context) {
+            super(context);
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] b, String s, String[] c, String d) {
+            throw new SQLiteException();
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when BluetoothMapService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_map));
+    }
+
+    @Test
+    public void testInitMsgList() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Context mockContext = mock(Context.class);
+        MockContentResolver mockResolver = new MockContentResolver();
+        ExceptionTestProvider mockProvider = new ExceptionTestProvider(mockContext);
+        mockResolver.addProvider("sms", mockProvider);
+
+        TelephonyManager mockTelephony = mock(TelephonyManager.class);
+        UserManager mockUserService = mock(UserManager.class);
+        BluetoothMapMasInstance mockMas = mock(BluetoothMapMasInstance.class);
+
+        // Functions that get called when BluetoothMapContentObserver is created
+        when(mockUserService.isUserUnlocked()).thenReturn(true);
+        when(mockContext.getContentResolver()).thenReturn(mockResolver);
+        when(mockContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mockTelephony);
+        when(mockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mockUserService);
+
+        BluetoothMapContentObserver observer;
+        try {
+            // The constructor of BluetoothMapContentObserver calls initMsgList
+            observer = new BluetoothMapContentObserver(mockContext, null, mockMas, null, true);
+        } catch (RemoteException e) {
+            Assert.fail("Failed to created BluetoothMapContentObserver object");
+        } catch (SQLiteException e) {
+            Assert.fail("Threw SQLiteException instead of Assert.failing cleanly");
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java b/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
new file mode 100644
index 0000000..34c64c5
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.map;
+
+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 BluetoothMapServiceTest {
+    private BluetoothMapService 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 BluetoothMapService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_map));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, BluetoothMapService.class);
+        mService = BluetoothMapService.getBluetoothMapService();
+        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_map)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, BluetoothMapService.class);
+        mService = BluetoothMapService.getBluetoothMapService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(BluetoothMapService.getBluetoothMapService());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
new file mode 100644
index 0000000..b83cf3d
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2017 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 static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.SdpMasRecord;
+import android.content.Context;
+import android.content.Intent;
+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 com.android.bluetooth.R;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+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 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;
+
+    @Mock
+    private MasClient mMockMasClient;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when MapClientService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce));
+
+        // 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);
+        Assert.assertNotNull(mMceStateMachine);
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        mHandler = new Handler();
+    }
+
+    @After
+    public void tearDown() {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce)) {
+            return;
+        }
+        if (mMceStateMachine != null) {
+            mMceStateMachine.doQuit();
+        }
+    }
+
+    /**
+     * Test that default state is STATE_CONNECTING
+     */
+    @Test
+    public void testDefaultConnectingState() {
+        Log.i(TAG, "in testDefaultConnectingState");
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());
+    }
+
+    /**
+     * Test transition from
+     *      STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
+     */
+    @Test
+    public void testStateTransitionFromConnectingToDisconnected() {
+        Log.i(TAG, "in testStateTransitionFromConnectingToDisconnected");
+        setupSdpRecordReceipt();
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED);
+        mMceStateMachine.getCurrentState().processMessage(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.
+    }
+
+    /**
+     * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED
+     */
+    @Test
+    public void testStateTransitionFromConnectingToConnected() {
+        Log.i(TAG, "in testStateTransitionFromConnectingToConnected");
+
+        setupSdpRecordReceipt();
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
+        mMceStateMachine.getCurrentState().processMessage(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);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+    }
+
+    private void setupSdpRecordReceipt() {
+        // Setup receipt of SDP record
+        SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "blah");
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_SDP_DONE, record);
+        mMceStateMachine.getCurrentState().processMessage(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
new file mode 100644
index 0000000..a8ac084
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 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 android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MapClientTest {
+    private static final String TAG = MapClientTest.class.getSimpleName();
+    private MapClientService mService = null;
+    private BluetoothAdapter mAdapter = null;
+    private Context mTargetContext;
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private MnsService mMockMnsService;
+
+    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when MapClientService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        MapUtils.setMnsService(mMockMnsService);
+        TestUtils.startService(mServiceRule, MapClientService.class);
+        mService = MapClientService.getMapClientService();
+        Assert.assertNotNull(mService);
+        cleanUpInstanceMap();
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, MapClientService.class);
+        mService = MapClientService.getMapClientService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    private void cleanUpInstanceMap() {
+        if (!mService.getInstanceMap().isEmpty()) {
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            for (BluetoothDevice d : deviceList) {
+                mService.disconnect(d);
+            }
+        }
+        Assert.assertTrue(mService.getInstanceMap().isEmpty());
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(MapClientService.getMapClientService());
+    }
+
+    /**
+     * Test connection of one device.
+     */
+    @Test
+    public void testConnect() {
+        // 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
+        Assert.assertTrue(mService.connect(device));
+
+        // is the statemachine created
+        Map<BluetoothDevice, MceStateMachine> map = mService.getInstanceMap();
+        Assert.assertEquals(1, map.size());
+        Assert.assertNotNull(map.get(device));
+    }
+
+    /**
+     * Test connecting MAXIMUM_CONNECTED_DEVICES devices.
+     */
+    @Test
+    public void testConnectMaxDevices() {
+        // Create bluetoothdevice & mock statemachine objects to be used in this test
+        List<BluetoothDevice> list = new ArrayList<>();
+        String address = "11:11:11:11:11:1";
+        for (int i = 0; i < MapClientService.MAXIMUM_CONNECTED_DEVICES; ++i) {
+            list.add(makeBluetoothDevice(address + i));
+        }
+
+        // make sure there is no statemachine already defined for the devices defined above
+        for (BluetoothDevice d : list) {
+            Assert.assertNull(mService.getInstanceMap().get(d));
+        }
+
+        // run the test - connect all devices
+        for (BluetoothDevice d : list) {
+            Assert.assertTrue(mService.connect(d));
+        }
+
+        // verify
+        Map<BluetoothDevice, MceStateMachine> map = mService.getInstanceMap();
+        Assert.assertEquals(MapClientService.MAXIMUM_CONNECTED_DEVICES, map.size());
+        for (BluetoothDevice d : list) {
+            Assert.assertNotNull(map.get(d));
+        }
+
+        // Try to connect one more device. Should fail.
+        BluetoothDevice last = makeBluetoothDevice("11:22:33:44:55:66");
+        Assert.assertFalse(mService.connect(last));
+    }
+
+    private BluetoothDevice makeBluetoothDevice(String address) {
+        return mAdapter.getRemoteDevice(address);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
new file mode 100644
index 0000000..66eac21
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2017 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 static org.mockito.Mockito.*;
+
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BrowserPlayerWrapperTest {
+
+    @Captor ArgumentCaptor<MediaBrowser.ConnectionCallback> mBrowserConnCb;
+    @Captor ArgumentCaptor<MediaBrowser.SubscriptionCallback> mSubscriptionCb;
+    @Captor ArgumentCaptor<List<ListItem>> mWrapperBrowseCb;
+    @Mock MediaBrowser mMockBrowser;
+    @Mock BrowsedPlayerWrapper.ConnectionCallback mConnCb;
+    @Mock BrowsedPlayerWrapper.BrowseCallback mBrowseCb;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mMockBrowser.getRoot()).thenReturn("root_folder");
+
+        MediaBrowserFactory.inject(mMockBrowser);
+    }
+
+    @Test
+    public void testWrap() {
+        BrowsedPlayerWrapper wrapper = BrowsedPlayerWrapper.wrap(null, "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        verify(mMockBrowser).connect();
+
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+        browserConnCb.onConnected();
+
+        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq(wrapper));
+        verify(mMockBrowser).disconnect();
+    }
+
+    @Test
+    public void testConnect() {
+        BrowsedPlayerWrapper wrapper = BrowsedPlayerWrapper.wrap(null, "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+        browserConnCb.onConnectionFailed();
+
+        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
+
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser, times(2)).connect();
+
+        browserConnCb.onConnected();
+        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq(wrapper));
+        verify(mMockBrowser, times(2)).disconnect();
+    }
+
+    @Test
+    public void testEmptyRoot() {
+        BrowsedPlayerWrapper wrapper = BrowsedPlayerWrapper.wrap(null, "test", "test");
+
+        doReturn("").when(mMockBrowser).getRoot();
+
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        verify(mMockBrowser, times(1)).connect();
+
+        browserConnCb.onConnected();
+        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
+        verify(mMockBrowser, times(1)).disconnect();
+    }
+
+    @Test
+    public void testDisconnect() {
+        BrowsedPlayerWrapper wrapper = BrowsedPlayerWrapper.wrap(null, "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+        browserConnCb.onConnected();
+        verify(mMockBrowser).disconnect();
+    }
+
+    @Test
+    public void testGetRootId() {
+        BrowsedPlayerWrapper wrapper = BrowsedPlayerWrapper.wrap(null, "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+        browserConnCb.onConnected();
+
+        Assert.assertEquals("root_folder", wrapper.getRootId());
+        verify(mMockBrowser).disconnect();
+    }
+
+    @Test
+    public void testPlayItem() {
+        BrowsedPlayerWrapper wrapper = BrowsedPlayerWrapper.wrap(null, "test", "test");
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        wrapper.playItem("test_item");
+        verify(mMockBrowser, times(1)).connect();
+
+        MediaController mockController = mock(MediaController.class);
+        MediaController.TransportControls mockTransport =
+                mock(MediaController.TransportControls.class);
+        when(mockController.getTransportControls()).thenReturn(mockTransport);
+        MediaControllerFactory.inject(mockController);
+
+        browserConnCb.onConnected();
+        verify(mockTransport).playFromMediaId(eq("test_item"), eq(null));
+        verify(mMockBrowser).disconnect();
+    }
+
+    @Test
+    public void testGetFolderItems() {
+        BrowsedPlayerWrapper wrapper = BrowsedPlayerWrapper.wrap(null, "test", "test");
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        wrapper.getFolderItems("test_folder", mBrowseCb);
+
+
+        browserConnCb.onConnected();
+        verify(mMockBrowser).subscribe(any(), mSubscriptionCb.capture());
+        MediaBrowser.SubscriptionCallback subscriptionCb = mSubscriptionCb.getValue();
+
+        ArrayList<MediaItem> items = new ArrayList<MediaItem>();
+        MediaDescription.Builder bob = new MediaDescription.Builder();
+        bob.setTitle("test_song1");
+        bob.setMediaId("ts1");
+        items.add(new MediaItem(bob.build(), 0));
+        bob.setTitle("test_song2");
+        bob.setMediaId("ts2");
+        items.add(new MediaItem(bob.build(), 0));
+
+        subscriptionCb.onChildrenLoaded("test_folder", items);
+        verify(mMockBrowser).unsubscribe(eq("test_folder"));
+        verify(mBrowseCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq("test_folder"),
+                mWrapperBrowseCb.capture());
+
+        List<ListItem> item_list = mWrapperBrowseCb.getValue();
+        for (int i = 0; i < item_list.size(); i++) {
+            Assert.assertFalse(item_list.get(i).isFolder);
+            Assert.assertEquals(item_list.get(i).song, Util.toMetadata(items.get(i)));
+        }
+
+        verify(mMockBrowser).disconnect();
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
new file mode 100644
index 0000000..85ad147
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
@@ -0,0 +1,684 @@
+/*
+ * Copyright 2017 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 static org.mockito.Mockito.*;
+
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+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 org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MediaPlayerWrapperTest {
+    private static final int MSG_TIMEOUT = 0;
+
+    private HandlerThread mThread;
+    private MediaMetadata.Builder mTestMetadata;
+    private ArrayList<MediaDescription.Builder> mTestQueue;
+    private PlaybackState.Builder mTestState;
+
+    @Captor ArgumentCaptor<MediaController.Callback> mControllerCbs;
+    @Captor ArgumentCaptor<MediaData> mMediaUpdateData;
+    @Mock Log.TerribleFailureHandler mFailHandler;
+    @Mock MediaController mMockController;
+    @Mock MediaPlayerWrapper.Callback mTestCbs;
+
+    List<MediaSession.QueueItem> getQueueFromDescriptions(
+            List<MediaDescription.Builder> descriptions) {
+        ArrayList<MediaSession.QueueItem> newList = new ArrayList<MediaSession.QueueItem>();
+
+        for (MediaDescription.Builder bob : descriptions) {
+            newList.add(
+                    new MediaSession.QueueItem(
+                            bob.build(), Long.valueOf(bob.build().getMediaId())));
+        }
+
+        return newList;
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        // Set failure handler to capture Log.wtf messages
+        Log.setWtfHandler(mFailHandler);
+
+        // Set up Looper thread for the timeout handler
+        mThread = new HandlerThread("MediaPlayerWrapperTestThread");
+        mThread.start();
+
+        // Set up new metadata that can be used in each test
+        mTestMetadata =
+                new MediaMetadata.Builder()
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, "BT Test Song")
+                        .putString(MediaMetadata.METADATA_KEY_ARTIST, "BT Test Artist")
+                        .putString(MediaMetadata.METADATA_KEY_ALBUM, "BT Test Album")
+                        .putLong(MediaMetadata.METADATA_KEY_DURATION, 5000L);
+
+        mTestState =
+                new PlaybackState.Builder()
+                        .setActiveQueueItemId(100)
+                        .setState(PlaybackState.STATE_PAUSED, 0, 1.0f);
+
+        mTestQueue = new ArrayList<MediaDescription.Builder>();
+        mTestQueue.add(
+                new MediaDescription.Builder()
+                        .setTitle("BT Test Song")
+                        .setSubtitle("BT Test Artist")
+                        .setDescription("BT Test Album")
+                        .setMediaId("100"));
+        mTestQueue.add(
+                new MediaDescription.Builder()
+                        .setTitle("BT Test Song 2")
+                        .setSubtitle("BT Test Artist 2")
+                        .setDescription("BT Test Album 2")
+                        .setMediaId("101"));
+        mTestQueue.add(
+                new MediaDescription.Builder()
+                        .setTitle("BT Test Song 3")
+                        .setSubtitle("BT Test Artist 3")
+                        .setDescription("BT Test Album 3")
+                        .setMediaId("102"));
+
+        when(mMockController.getPackageName()).thenReturn("mMockController");
+        // NOTE: We use doReturn below because using the normal stubbing method
+        // doesn't immediately update the stub with the new return value and this
+        // can cause the old stub to be used.
+
+        // Stub default metadata for the Media Controller
+        doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+        doReturn(getQueueFromDescriptions(mTestQueue)).when(mMockController).getQueue();
+
+        // Enable testing flag which enables Log.wtf statements. Some tests test against improper
+        // behaviour and the TerribleFailureListener is a good way to ensure that the error occurred
+        MediaPlayerWrapper.sTesting = true;
+    }
+
+    /*
+     * Test to make sure that the wrapper fails to be built if passed invalid
+     * data.
+     */
+    @Test
+    public void testNullControllerLooper() {
+        MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(null, mThread.getLooper());
+        Assert.assertNull(wrapper);
+
+        wrapper = MediaPlayerWrapper.wrap(mMockController, null);
+        Assert.assertNull(wrapper);
+    }
+
+    /*
+     * Test to make sure that isReady() returns false if there is no playback state,
+     * there is no metadata, or if the metadata has no title.
+     */
+    @Test
+    public void testIsReady() {
+        MediaPlayerWrapper wrapper = MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        Assert.assertTrue(wrapper.isReady());
+
+        // Test isReady() is false when the playback state is null
+        doReturn(null).when(mMockController).getPlaybackState();
+        Assert.assertFalse(wrapper.isReady());
+
+        // Restore the old playback state
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+        Assert.assertTrue(wrapper.isReady());
+
+        // Test isReady() is false when the metadata is null
+        doReturn(null).when(mMockController).getMetadata();
+        Assert.assertFalse(wrapper.isReady());
+
+        // Restore the old metadata
+        doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
+        Assert.assertTrue(wrapper.isReady());
+    }
+
+    /*
+     * Test to make sure that if a new controller is registered with different metadata than the
+     * previous controller, the new metadata is pulled upon registration.
+     */
+    @Test
+    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());
+        wrapper.registerCallback(mTestCbs);
+
+        // Create a new MediaController that has different metadata than the previous controller
+        MediaController mUpdatedController = mock(MediaController.class);
+        doReturn(mTestState.build()).when(mUpdatedController).getPlaybackState();
+        mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
+        doReturn(mTestMetadata.build()).when(mUpdatedController).getMetadata();
+        doReturn(null).when(mMockController).getQueue();
+
+        // Update the wrappers controller to the new controller
+        wrapper.updateMediaController(mUpdatedController);
+
+        // Send a metadata update with the same data that the controller had upon registering
+        verify(mUpdatedController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+        controllerCallbacks.onMetadataChanged(mTestMetadata.build());
+
+        // Verify that a callback was never called since no data was updated
+        verify(mTestCbs, never()).mediaUpdatedCallback(any());
+    }
+
+    /*
+     * Test to make sure that a media player update gets sent whenever a Media metadata or playback
+     * state change occurs instead of waiting for all data to be synced if the player doesn't
+     * support queues.
+     */
+    @Test
+    public void testNoQueueMediaUpdates() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Return null when getting the queue
+        doReturn(null).when(mMockController).getQueue();
+
+        // Grab the callbacks the wrapper registered with the controller
+        verify(mMockController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+
+        // Update Metdata returned by controller
+        mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
+        doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
+        controllerCallbacks.onMetadataChanged(mTestMetadata.build());
+
+        // Assert that the metadata was updated and playback state wasn't
+        verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
+        MediaData data = mMediaUpdateData.getValue();
+        Assert.assertEquals(
+                "Returned Metadata isn't equal to given Metadata",
+                data.metadata,
+                Util.toMetadata(mTestMetadata.build()));
+        Assert.assertEquals(
+                "Returned PlaybackState isn't equal to original PlaybackState",
+                data.state.toString(),
+                mTestState.build().toString());
+        Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
+
+        // Update PlaybackState returned by controller
+        mTestState.setActiveQueueItemId(103);
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+        controllerCallbacks.onPlaybackStateChanged(mTestState.build());
+
+        // Assert that the PlaybackState was changed but metadata stayed the same
+        verify(mTestCbs, times(2)).mediaUpdatedCallback(mMediaUpdateData.capture());
+        data = mMediaUpdateData.getValue();
+        Assert.assertEquals(
+                "Returned PlaybackState isn't equal to given PlaybackState",
+                data.state.toString(),
+                mTestState.build().toString());
+        Assert.assertEquals(
+                "Returned Metadata isn't equal to given Metadata",
+                data.metadata,
+                Util.toMetadata(mTestMetadata.build()));
+        Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
+
+        // Verify that there are no timeout messages pending and there were no timeouts
+        Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
+        verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
+    }
+
+    /*
+     * This test updates the metadata and playback state returned by the
+     * controller then sends an update. This is to make sure that all relevant
+     * information is sent with every update. In the case without a queue,
+     * metadata and playback state are updated.
+     */
+    @Test
+    public void testDataOnUpdateNoQueue() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Return null when getting the queue
+        doReturn(null).when(mMockController).getQueue();
+
+        // Grab the callbacks the wrapper registered with the controller
+        verify(mMockController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+
+        // Update Metadata returned by controller
+        mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
+        doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
+
+        // Update PlaybackState returned by controller
+        mTestState.setActiveQueueItemId(103);
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+
+        // Call the callback
+        controllerCallbacks.onPlaybackStateChanged(mTestState.build());
+
+        // Assert that both metadata and playback state are there.
+        verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
+        MediaData data = mMediaUpdateData.getValue();
+        Assert.assertEquals(
+                "Returned PlaybackState isn't equal to given PlaybackState",
+                data.state.toString(),
+                mTestState.build().toString());
+        Assert.assertEquals(
+                "Returned Metadata isn't equal to given Metadata",
+                data.metadata,
+                Util.toMetadata(mTestMetadata.build()));
+        Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
+
+        // Verify that there are no timeout messages pending and there were no timeouts
+        Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
+        verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
+    }
+
+    @Test
+    public void testNullMetadata() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Return null when getting the queue
+        doReturn(null).when(mMockController).getQueue();
+
+        // Grab the callbacks the wrapper registered with the controller
+        verify(mMockController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+
+        // Update Metadata returned by controller
+        mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
+        doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
+
+        // Call the callback
+        controllerCallbacks.onMetadataChanged(null);
+
+        // Assert that the metadata returned by getMetadata() is used instead of null
+        verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
+        MediaData data = mMediaUpdateData.getValue();
+        Assert.assertEquals("Returned metadata is incorrect", data.metadata,
+                Util.toMetadata(mTestMetadata.build()));
+    }
+
+    @Test
+    public void testNullQueue() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Return null when getting the queue
+        doReturn(null).when(mMockController).getQueue();
+
+        // Grab the callbacks the wrapper registered with the controller
+        verify(mMockController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+
+        // Call the callback
+        controllerCallbacks.onQueueChanged(null);
+
+        // Assert that both metadata and playback state are there.
+        verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
+        MediaData data = mMediaUpdateData.getValue();
+        Assert.assertEquals("Returned Queue isn't null", data.queue.size(), 0);
+    }
+
+    /*
+     * This test checks to see if the now playing queue data is cached.
+     */
+    @Test
+    public void testQueueCached() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Call getCurrentQueue() multiple times.
+        for (int i = 0; i < 3; i++) {
+            Assert.assertEquals(wrapper.getCurrentQueue(),
+                    Util.toMetadataList(getQueueFromDescriptions(mTestQueue)));
+        }
+
+        // Verify that getQueue() was only called twice. Once on creation and once during
+        // registration
+        verify(mMockController, times(2)).getQueue();
+    }
+
+    /*
+     * This test sends repeated Playback State updates that only have a short
+     * position update change to see if they get debounced.
+     */
+    @Test
+    public void testPlaybackStateUpdateSpam() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Return null when getting the queue
+        doReturn(null).when(mMockController).getQueue();
+
+        // Grab the callbacks the wrapper registered with the controller
+        verify(mMockController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+
+        // Update PlaybackState returned by controller (Should trigger update)
+        mTestState.setState(PlaybackState.STATE_PLAYING, 1000, 1.0f);
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+        controllerCallbacks.onPlaybackStateChanged(mTestState.build());
+
+        // Assert that both metadata and only the first playback state is there.
+        verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
+        MediaData data = mMediaUpdateData.getValue();
+        Assert.assertEquals(
+                "Returned PlaybackState isn't equal to given PlaybackState",
+                data.state.toString(),
+                mTestState.build().toString());
+        Assert.assertEquals(
+                "Returned Metadata isn't equal to given Metadata",
+                data.metadata,
+                Util.toMetadata(mTestMetadata.build()));
+        Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
+
+        // Update PlaybackState returned by controller (Shouldn't trigger update)
+        mTestState.setState(PlaybackState.STATE_PLAYING, 1020, 1.0f);
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+        controllerCallbacks.onPlaybackStateChanged(mTestState.build());
+
+        // Update PlaybackState returned by controller (Shouldn't trigger update)
+        mTestState.setState(PlaybackState.STATE_PLAYING, 1040, 1.0f);
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+        controllerCallbacks.onPlaybackStateChanged(mTestState.build());
+
+        // Update PlaybackState returned by controller (Should trigger update)
+        mTestState.setState(PlaybackState.STATE_PLAYING, 3000, 1.0f);
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+        controllerCallbacks.onPlaybackStateChanged(mTestState.build());
+
+
+        // Verify that there are no timeout messages pending and there were no timeouts
+        Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
+        verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
+    }
+
+    /*
+     * Check to make sure that cleanup tears down the object properly
+     */
+    @Test
+    public void testWrapperCleanup() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Cleanup the wrapper
+        wrapper.cleanup();
+
+        // Ensure that everything was cleaned up
+        verify(mMockController).unregisterCallback(any());
+        Assert.assertNull(wrapper.getTimeoutHandler());
+    }
+
+    /*
+     * Test to check that a PlaybackState of none is being ignored as that usually means that the
+     * MediaController isn't ready.
+     */
+    @Test
+    public void testIgnorePlaystateNone() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Grab the callbacks the wrapper registered with the controller
+        verify(mMockController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+
+        // Update PlaybackState returned by controller
+        mTestState.setState(PlaybackState.STATE_NONE, 0, 1.0f);
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+        controllerCallbacks.onPlaybackStateChanged(mTestState.build());
+
+        // Verify that there was no update
+        verify(mTestCbs, never()).mediaUpdatedCallback(any());
+
+        // Verify that there are no timeout messages pending and there were no timeouts
+        Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
+        verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
+    }
+
+    /*
+     * Test to make sure that on a controller that supports browsing, the
+     * Media Metadata, Queue, and Playback state all have to be in sync in
+     * order for a media update to be sent via registered callback.
+     */
+    @Test
+    public void testMetadataSync() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Grab the callbacks the wrapper registered with the controller
+        verify(mMockController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+
+        // Update Metadata returned by controller
+        mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "New Title");
+        doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
+        controllerCallbacks.onMetadataChanged(mTestMetadata.build());
+
+        // Update PlaybackState returned by controller
+        mTestState.setActiveQueueItemId(103);
+        doReturn(mTestState.build()).when(mMockController).getPlaybackState();
+        controllerCallbacks.onPlaybackStateChanged(mTestState.build());
+
+        // Update Queue returned by controller
+        mTestQueue.add(
+                new MediaDescription.Builder()
+                        .setTitle("New Title")
+                        .setSubtitle("BT Test Artist")
+                        .setDescription("BT Test Album")
+                        .setMediaId("103"));
+        doReturn(getQueueFromDescriptions(mTestQueue)).when(mMockController).getQueue();
+        controllerCallbacks.onQueueChanged(getQueueFromDescriptions(mTestQueue));
+
+        // Assert that the callback was called with the updated data
+        verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
+        verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
+        MediaData data = mMediaUpdateData.getValue();
+        Assert.assertEquals(
+                "Returned Metadata isn't equal to given Metadata",
+                data.metadata,
+                Util.toMetadata(mTestMetadata.build()));
+        Assert.assertEquals(
+                "Returned PlaybackState isn't equal to given PlaybackState",
+                data.state.toString(),
+                mTestState.build().toString());
+        Assert.assertEquals(
+                "Returned Queue isn't equal to given Queue",
+                data.queue,
+                Util.toMetadataList(getQueueFromDescriptions(mTestQueue)));
+
+        // Verify that there are no timeout messages pending and there were no timeouts
+        Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
+        verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
+    }
+
+    /*
+     * Test to make sure that an error occurs when the MediaController fails to
+     * update all its media data in a resonable amount of time.
+     */
+    @Test
+    public void testMetadataSyncFail() {
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager =
+                InstrumentationRegistry.getInstrumentation()
+                        .acquireLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Grab the callbacks the wrapper registered with the controller
+        verify(mMockController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+
+        // Update Metadata returned by controller
+        mTestMetadata.putString(MediaMetadata.METADATA_KEY_TITLE, "Mismatch Title");
+        doReturn(mTestMetadata.build()).when(mMockController).getMetadata();
+        controllerCallbacks.onMetadataChanged(mTestMetadata.build());
+
+        // Force the timeout to execute immediately
+        looperManager.execute(looperManager.next());
+
+        // Assert that there was a timeout
+        verify(mFailHandler).onTerribleFailure(any(), any(), anyBoolean());
+
+        // Assert that the callback was called with the mismatch data
+        verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
+        MediaData data = mMediaUpdateData.getValue();
+        Assert.assertEquals(
+                "Returned Metadata isn't equal to given Metadata",
+                data.metadata,
+                Util.toMetadata(mTestMetadata.build()));
+        Assert.assertEquals(
+                "Returned PlaybackState isn't equal to given PlaybackState",
+                data.state.toString(),
+                mTestState.build().toString());
+        Assert.assertEquals(
+                "Returned Queue isn't equal to given Queue",
+                data.queue,
+                Util.toMetadataList(getQueueFromDescriptions(mTestQueue)));
+    }
+
+    /*
+     * testMetadataSyncFuzz() tests for the same conditions as testMetadataSync()
+     * but randomizes the order in which the MediaController update callbacks are
+     * called. The test is repeated 100 times for completeness.
+     */
+    @Test
+    public void testMetadataSyncFuzz() {
+        // The number of times the random order test is run
+        final int numTestLoops = 100;
+
+        // Create the wrapper object and register the looper with the timeout handler
+        TestLooperManager looperManager =
+                InstrumentationRegistry.getInstrumentation()
+                        .acquireLooperManager(mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapper.wrap(mMockController, mThread.getLooper());
+        wrapper.registerCallback(mTestCbs);
+
+        // Grab the callbacks the wrapper registered with the controller
+        verify(mMockController).registerCallback(mControllerCbs.capture(), any());
+        MediaController.Callback controllerCallbacks = mControllerCbs.getValue();
+
+        MediaMetadata.Builder m = new MediaMetadata.Builder();
+        PlaybackState.Builder s = new PlaybackState.Builder();
+        s.setState(PlaybackState.STATE_PAUSED, 0, 1.0f);
+        MediaDescription.Builder d = new MediaDescription.Builder();
+        for (int i = 1; i <= numTestLoops; i++) {
+            // Setup Media Info for current itteration
+            m.putString(MediaMetadata.METADATA_KEY_TITLE, "BT Fuzz Song " + i);
+            m.putString(MediaMetadata.METADATA_KEY_ARTIST, "BT Fuzz Artist " + i);
+            m.putString(MediaMetadata.METADATA_KEY_ALBUM, "BT Fuzz Album " + i);
+            m.putLong(MediaMetadata.METADATA_KEY_DURATION, 5000L);
+            s.setActiveQueueItemId(i);
+            d.setTitle("BT Fuzz Song " + i);
+            d.setSubtitle("BT Fuzz Artist " + i);
+            d.setDescription("BT Fuzz Album " + i);
+            d.setMediaId(Integer.toString(i));
+
+            // Create a new Queue each time to prevent double counting caused by
+            // Playback State matching the updated Queue
+            ArrayList<MediaSession.QueueItem> q = new ArrayList<MediaSession.QueueItem>();
+            q.add(new MediaSession.QueueItem(d.build(), i));
+
+            // Call the MediaController callbacks in a random order
+            ArrayList<Integer> callbackOrder = new ArrayList<>(Arrays.asList(0, 1, 2));
+            Collections.shuffle(callbackOrder);
+            for (int j = 0; j < 3; j++) {
+                switch (callbackOrder.get(j)) {
+                    case 0: // Update Metadata
+                        doReturn(m.build()).when(mMockController).getMetadata();
+                        controllerCallbacks.onMetadataChanged(m.build());
+                        break;
+                    case 1: // Update PlaybackState
+                        doReturn(s.build()).when(mMockController).getPlaybackState();
+                        controllerCallbacks.onPlaybackStateChanged(s.build());
+                        break;
+                    case 2: // Update Queue
+                        doReturn(q).when(mMockController).getQueue();
+                        controllerCallbacks.onQueueChanged(q);
+                        break;
+                }
+            }
+
+            // Check that the callback was called a certain number of times and
+            // that all the Media info matches what was given
+            verify(mTestCbs, times(i)).mediaUpdatedCallback(mMediaUpdateData.capture());
+            MediaData data = mMediaUpdateData.getValue();
+            Assert.assertEquals(
+                    "Returned Metadata isn't equal to given Metadata",
+                    data.metadata,
+                    Util.toMetadata(m.build()));
+            Assert.assertEquals(
+                    "Returned PlaybackState isn't equal to given PlaybackState",
+                    data.state.toString(),
+                    s.build().toString());
+            Assert.assertEquals("Returned Queue isn't equal to given Queue",
+                    data.queue,
+                    Util.toMetadataList(q));
+        }
+
+        // Verify that there are no timeout messages pending and there were no timeouts
+        Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
+        verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java b/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
new file mode 100644
index 0000000..8b40c62
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.opp;
+
+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 BluetoothOppServiceTest {
+    private BluetoothOppService 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 BluetoothOppService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_opp));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, BluetoothOppService.class);
+        mService = BluetoothOppService.getBluetoothOppService();
+        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_opp)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, BluetoothOppService.class);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(BluetoothOppService.getBluetoothOppService());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
new file mode 100644
index 0000000..ca96285
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.pan;
+
+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 PanServiceTest {
+    private PanService 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 PanService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_pan));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, PanService.class);
+        mService = PanService.getPanService();
+        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_pan)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, PanService.class);
+        mService = PanService.getPanService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(PanService.getPanService());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java b/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
new file mode 100644
index 0000000..e3dcf8c
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.pbap;
+
+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 BluetoothPbapServiceTest {
+    private BluetoothPbapService mService;
+    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 BluetoothPbapService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_pbap));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, BluetoothPbapService.class);
+        mService = BluetoothPbapService.getBluetoothPbapService();
+        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_pbap)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, BluetoothPbapService.class);
+        mService = BluetoothPbapService.getBluetoothPbapService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(BluetoothPbapService.getBluetoothPbapService());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java b/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java
new file mode 100644
index 0000000..0f085e8
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/pbap/PbapStateMachineTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2017 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.pbap;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSocket;
+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 com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+
+import org.hamcrest.core.IsInstanceOf;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PbapStateMachineTest {
+    private static final int TEST_NOTIFICATION_ID = 1000000;
+
+    private Context mTargetContext;
+    private BluetoothAdapter mAdapter;
+    private HandlerThread mHandlerThread;
+    private PbapStateMachine mPbapStateMachine;
+    private BluetoothDevice mTestDevice;
+    private Handler mHandler;
+    private BluetoothSocket mSocket;
+    private BluetoothPbapService mBluetoothPbapService;
+
+    @Mock private AdapterService mAdapterService;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when BluetoothPbapService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_pbap));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        // 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");
+
+        mHandlerThread = new HandlerThread("PbapTestHandlerThread");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mBluetoothPbapService = mock(BluetoothPbapService.class);
+        doNothing().when(mBluetoothPbapService).checkOrGetPhonebookPermission(any());
+        mPbapStateMachine = PbapStateMachine.make(mBluetoothPbapService, mHandlerThread.getLooper(),
+                mTestDevice, mSocket, mBluetoothPbapService, mHandler, TEST_NOTIFICATION_ID);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_pbap)) {
+            return;
+        }
+        mHandlerThread.quitSafely();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    /**
+     * Test that initial state is WaitingForAuth
+     */
+    @Test
+    public void testInitialState() {
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+                mPbapStateMachine.getConnectionState());
+        Assert.assertThat(mPbapStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(PbapStateMachine.WaitingForAuth.class));
+    }
+
+    /**
+     * Test state transition from WaitingForAuth to Finished when the user rejected
+     */
+    @Ignore("Class BluetoothSocket is final and cannot be mocked. b/71512958: re-enable it.")
+    @Test
+    public void testStateTransition_WaitingForAuthToFinished() throws Exception {
+        mPbapStateMachine.sendMessage(PbapStateMachine.REJECTED);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mPbapStateMachine.getConnectionState());
+        Assert.assertThat(mPbapStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(PbapStateMachine.Finished.class));
+    }
+
+    /**
+     * Test state transition from WaitingForAuth to Finished when the user rejected
+     */
+    @Ignore("Class BluetoothSocket is final and cannot be mocked. b/71512958: re-enable it.")
+    @Test
+    public void testStateTransition_WaitingForAuthToConnected() throws Exception {
+        mPbapStateMachine.sendMessage(PbapStateMachine.AUTHORIZED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mPbapStateMachine.getConnectionState());
+        Assert.assertThat(mPbapStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(PbapStateMachine.Connected.class));
+    }
+
+    /**
+     * Test state transition from Connected to Finished when the OBEX server is done
+     */
+    @Ignore("Class BluetoothSocket is final and cannot be mocked. b/71512958: re-enable it.")
+    @Test
+    public void testStateTransition_ConnectedToFinished() throws Exception {
+        mPbapStateMachine.sendMessage(PbapStateMachine.AUTHORIZED);
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+                mPbapStateMachine.getConnectionState());
+        Assert.assertThat(mPbapStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(PbapStateMachine.Connected.class));
+
+        // PBAP OBEX transport is done.
+        mPbapStateMachine.sendMessage(PbapStateMachine.DISCONNECT);
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mPbapStateMachine.getConnectionState());
+        Assert.assertThat(mPbapStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(PbapStateMachine.Finished.class));
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java b/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
new file mode 100644
index 0000000..dd33d79
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.pbapclient;
+
+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 PbapClientServiceTest {
+    private PbapClientService 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 PbapClientService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_pbapclient));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, PbapClientService.class);
+        mService = PbapClientService.getPbapClientService();
+        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_pbapclient)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, PbapClientService.class);
+        mService = PbapClientService.getPbapClientService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(PbapClientService.getPbapClientService());
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java b/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java
new file mode 100644
index 0000000..f8ad6bf
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/pbapclient/PbapParserTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2017 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.accounts.Account;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+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 com.android.bluetooth.R;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.TimeZone;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PbapParserTest {
+    private Account mAccount;
+    private Resources mTestResources;
+    private Context mTargetContext;
+    private static final String TEST_ACCOUNT_NAME = "PBAPTESTACCOUNT";
+    private static final String TEST_PACKAGE_NAME = "com.android.bluetooth.tests";
+
+    @Before
+    public void setUp() {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when PbapClientService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_pbapclient));
+        mAccount = new Account(TEST_ACCOUNT_NAME,
+                mTargetContext.getString(com.android.bluetooth.R.string.pbap_account_type));
+        try {
+            mTestResources = mTargetContext.getPackageManager()
+                    .getResourcesForApplication(TEST_PACKAGE_NAME);
+        } catch (PackageManager.NameNotFoundException e) {
+            Assert.fail("Setup Failure Unable to get resources" + e.toString());
+        }
+        cleanupCallLog();
+        cleanupPhonebook();
+    }
+
+    // testNoTimestamp should parse 1 poorly formed vcard and not crash.
+    @Test
+    public void testNoTimestamp() throws IOException {
+        InputStream fileStream;
+        fileStream = mTestResources.openRawResource(
+                com.android.bluetooth.tests.R.raw.no_timestamp_call_log);
+        BluetoothPbapVcardList pbapVCardList = new BluetoothPbapVcardList(mAccount, fileStream,
+                PbapClientConnectionHandler.VCARD_TYPE_30);
+        Assert.assertEquals(1, pbapVCardList.getCount());
+        CallLogPullRequest processor =
+                new CallLogPullRequest(mTargetContext, PbapClientConnectionHandler.MCH_PATH,
+                    new HashMap<>(), mAccount);
+        processor.setResults(pbapVCardList.getList());
+
+        // Verify that these entries aren't in the call log to start.
+        Assert.assertFalse(verifyCallLog("555-0001", null, "3"));
+
+        // Finish processing the data and verify entries were added to the call log.
+        processor.onPullComplete();
+        Assert.assertTrue(verifyCallLog("555-0001", null, "3"));
+    }
+
+    // testMissedCall should parse one phonecall correctly.
+    @Test
+    public void testMissedCall() throws IOException {
+        InputStream fileStream;
+        fileStream = mTestResources.openRawResource(
+                com.android.bluetooth.tests.R.raw.single_missed_call);
+        BluetoothPbapVcardList pbapVCardList = new BluetoothPbapVcardList(mAccount, fileStream,
+                PbapClientConnectionHandler.VCARD_TYPE_30);
+        Assert.assertEquals(1, pbapVCardList.getCount());
+        CallLogPullRequest processor =
+                new CallLogPullRequest(mTargetContext, PbapClientConnectionHandler.MCH_PATH,
+                    new HashMap<>(), mAccount);
+        processor.setResults(pbapVCardList.getList());
+
+        // Verify that these entries aren't in the call log to start.
+        Assert.assertFalse(verifyCallLog("555-0002", "1483232460000", "3"));
+        // Finish processing the data and verify entries were added to the call log.
+        processor.onPullComplete();
+        Assert.assertTrue(verifyCallLog("555-0002", "1483232460000", "3"));
+    }
+
+    // testUnknownCall should parse two calls with no phone number.
+    @Test
+    public void testUnknownCall() throws IOException {
+        InputStream fileStream;
+        fileStream = mTestResources.openRawResource(
+                com.android.bluetooth.tests.R.raw.unknown_number_call);
+        BluetoothPbapVcardList pbapVCardList = new BluetoothPbapVcardList(mAccount, fileStream,
+                PbapClientConnectionHandler.VCARD_TYPE_30);
+        Assert.assertEquals(2, pbapVCardList.getCount());
+        CallLogPullRequest processor =
+                new CallLogPullRequest(mTargetContext, PbapClientConnectionHandler.MCH_PATH,
+                    new HashMap<>(), mAccount);
+        processor.setResults(pbapVCardList.getList());
+
+        // Verify that these entries aren't in the call log to start.
+        Assert.assertFalse(verifyCallLog("", "1483232520000", "3"));
+        Assert.assertFalse(verifyCallLog("", "1483232580000", "3"));
+
+        // Finish processing the data and verify entries were added to the call log.
+        processor.onPullComplete();
+        Assert.assertTrue(verifyCallLog("", "1483232520000", "3"));
+        Assert.assertTrue(verifyCallLog("", "1483232580000", "3"));
+    }
+
+    @Test
+    public void testPullPhoneBook() throws IOException {
+        InputStream fileStream;
+        fileStream = mTestResources.openRawResource(
+                com.android.bluetooth.tests.R.raw.v30_simple);
+        BluetoothPbapVcardList pbapVCardList = new BluetoothPbapVcardList(mAccount, fileStream,
+                PbapClientConnectionHandler.VCARD_TYPE_30);
+        Assert.assertEquals(1, pbapVCardList.getCount());
+        PhonebookPullRequest processor = new PhonebookPullRequest(mTargetContext, mAccount);
+        processor.setResults(pbapVCardList.getList());
+        Assert.assertFalse(verifyPhonebook("Roid And", "0300000000"));
+        processor.onPullComplete();
+        Assert.assertTrue(verifyPhonebook("Roid And", "0300000000"));
+    }
+
+    private void cleanupCallLog() {
+        mTargetContext.getContentResolver().delete(Calls.CONTENT_URI, null, null);
+    }
+
+    private void cleanupPhonebook() {
+        mTargetContext.getContentResolver().delete(ContactsContract.RawContacts.CONTENT_URI,
+                null, null);
+    }
+
+    // Find Entries in call log with type matching number and date.
+    // If number or date is null it will match any number or date respectively.
+    private boolean verifyCallLog(String number, String date, String type) {
+        String[] query = new String[]{Calls.NUMBER, Calls.DATE, Calls.TYPE};
+        Cursor cursor = mTargetContext.getContentResolver()
+                .query(Calls.CONTENT_URI, query, Calls.TYPE + "= " + type, null,
+                        Calls.DATE + ", " + Calls.NUMBER);
+        if (date != null) {
+            date = adjDate(date);
+        }
+        if (cursor != null) {
+            while (cursor.moveToNext()) {
+                String foundNumber = cursor.getString(cursor.getColumnIndex(Calls.NUMBER));
+                String foundDate = cursor.getString(cursor.getColumnIndex(Calls.DATE));
+                if ((number == null || number.equals(foundNumber)) && (date == null || date.equals(
+                        foundDate))) {
+                    return true;
+                }
+            }
+            cursor.close();
+        }
+        return false;
+    }
+
+    // Get time zone from device and adjust date to the device's time zone.
+    private static String adjDate(String date) {
+        TimeZone tz = TimeZone.getDefault();
+        long dt = Long.valueOf(date) - tz.getRawOffset();
+        return Long.toString(dt);
+    }
+
+    private boolean verifyPhonebook(String name, String number) {
+        Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                Uri.encode(number));
+        Cursor c = mTargetContext.getContentResolver().query(uri, null, null, null);
+        if (c != null && c.getCount() > 0) {
+            c.moveToNext();
+            String displayName = c.getString(
+                    c.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME));
+            if (displayName.equals(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java b/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
new file mode 100644
index 0000000..8505673
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.sap;
+
+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 SapServiceTest {
+    private SapService 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 SapService is not enabled",
+                mTargetContext.getResources().getBoolean(R.bool.profile_supported_sap));
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+        TestUtils.startService(mServiceRule, SapService.class);
+        mService = SapService.getSapService();
+        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_sap)) {
+            return;
+        }
+        TestUtils.stopService(mServiceRule, SapService.class);
+        mService = SapService.getSapService();
+        Assert.assertNull(mService);
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testInitialize() {
+        Assert.assertNotNull(SapService.getSapService());
+    }
+}
diff --git a/tools/Intellij_Code_Style_Bluetooth.xml b/tools/Intellij_Code_Style_Bluetooth.xml
new file mode 100644
index 0000000..a707d8d
--- /dev/null
+++ b/tools/Intellij_Code_Style_Bluetooth.xml
@@ -0,0 +1,113 @@
+<code_scheme name="Android CheckStyle">
+  <option name="FIELD_NAME_PREFIX" value="m" />
+  <option name="STATIC_FIELD_NAME_PREFIX" value="s" />
+  <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
+  <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
+  <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
+    <value>
+      <package name="org.junit.Assert" withSubpackages="false" static="false" />
+      <package name="org.mockito.Mockito" withSubpackages="false" static="false" />
+      <package name="org.mockito.MockitoAnnotations" withSubpackages="false" static="false" />
+      <package name="org.hamcrest.Matchers" withSubpackages="false" static="false" />
+    </value>
+  </option>
+  <option name="IMPORT_LAYOUT_TABLE">
+    <value>
+      <package name="" withSubpackages="true" static="true" />
+      <emptyLine />
+      <package name="android" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="com.android" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="dalvik" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="com" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="gov" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="junit" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="libcore" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="net" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="org" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="java" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="javax" withSubpackages="true" static="false" />
+    </value>
+  </option>
+  <option name="RIGHT_MARGIN" value="100" />
+  <option name="ENABLE_JAVADOC_FORMATTING" value="false" />
+  <option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
+  <option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
+  <option name="JD_P_AT_EMPTY_LINES" value="false" />
+  <option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
+  <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
+  <option name="JD_KEEP_EMPTY_RETURN" value="false" />
+  <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
+  <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+  <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
+  <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+  <option name="ALIGN_MULTILINE_FOR" value="false" />
+  <option name="CALL_PARAMETERS_WRAP" value="1" />
+  <option name="METHOD_PARAMETERS_WRAP" value="1" />
+  <option name="EXTENDS_LIST_WRAP" value="1" />
+  <option name="THROWS_KEYWORD_WRAP" value="1" />
+  <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
+  <option name="BINARY_OPERATION_WRAP" value="1" />
+  <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
+  <option name="TERNARY_OPERATION_WRAP" value="1" />
+  <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+  <option name="FOR_STATEMENT_WRAP" value="1" />
+  <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+  <option name="WRAP_COMMENTS" value="true" />
+  <JavaCodeStyleSettings>
+    <option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
+    <option name="ANNOTATION_PARAMETER_WRAP" value="1" />
+  </JavaCodeStyleSettings>
+  <Python>
+    <option name="USE_CONTINUATION_INDENT_FOR_ARGUMENTS" value="true" />
+  </Python>
+  <editorconfig>
+    <option name="ENABLED" value="false" />
+  </editorconfig>
+  <codeStyleSettings language="JAVA">
+    <option name="KEEP_LINE_BREAKS" value="false" />
+    <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
+    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+    <option name="ALIGN_MULTILINE_RESOURCES" value="false" />
+    <option name="ALIGN_MULTILINE_FOR" value="false" />
+    <option name="CALL_PARAMETERS_WRAP" value="1" />
+    <option name="METHOD_PARAMETERS_WRAP" value="1" />
+    <option name="RESOURCE_LIST_WRAP" value="1" />
+    <option name="EXTENDS_LIST_WRAP" value="1" />
+    <option name="THROWS_LIST_WRAP" value="1" />
+    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+    <option name="THROWS_KEYWORD_WRAP" value="1" />
+    <option name="METHOD_CALL_CHAIN_WRAP" value="5" />
+    <option name="BINARY_OPERATION_WRAP" value="1" />
+    <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
+    <option name="TERNARY_OPERATION_WRAP" value="1" />
+    <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+    <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
+    <option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
+    <option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
+    <option name="FOR_STATEMENT_WRAP" value="1" />
+    <option name="ARRAY_INITIALIZER_WRAP" value="5" />
+    <option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
+    <option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
+    <option name="ASSIGNMENT_WRAP" value="1" />
+    <option name="ASSERT_STATEMENT_WRAP" value="1" />
+    <option name="ASSERT_STATEMENT_COLON_ON_NEXT_LINE" value="true" />
+    <option name="IF_BRACE_FORCE" value="3" />
+    <option name="DOWHILE_BRACE_FORCE" value="3" />
+    <option name="WHILE_BRACE_FORCE" value="3" />
+    <option name="FOR_BRACE_FORCE" value="3" />
+    <option name="WRAP_LONG_LINES" value="true" />
+    <option name="PARAMETER_ANNOTATION_WRAP" value="1" />
+    <option name="VARIABLE_ANNOTATION_WRAP" value="1" />
+    <option name="ENUM_CONSTANTS_WRAP" value="5" />
+  </codeStyleSettings>
+</code_scheme>
\ No newline at end of file