Merge "PBAP server, send favorite contacts"
diff --git a/.clang-format b/.clang-format
index 8b28743..85098f2 100644
--- a/.clang-format
+++ b/.clang-format
@@ -27,3 +27,4 @@
 Language: Java
 # Java format is handled by check_style hook
 DisableFormat: true
+SortIncludes: false
diff --git a/Android.bp b/Android.bp
index 726a790..08e0ee1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,3 +1,93 @@
-subdirs = [
-    "jni",
-]
+// MAP API module
+
+java_library {
+    name: "bluetooth.mapsapi",
+
+    srcs: ["lib/mapapi/**/*.java"],
+}
+
+// Bluetooth JNI
+
+cc_library_shared {
+    name: "libbluetooth_jni",
+    srcs: ["jni/**/*.cpp"],
+    header_libs: ["libbluetooth_headers"],
+    include_dirs: [
+        "system/bt/types",
+    ],
+    shared_libs: [
+        "libbase",
+        "libchrome",
+        "liblog",
+    ],
+    static_libs: [
+        "libbluetooth-types",
+        // TODO(b/148645937) move this back to shared_libs
+        "libnativehelper",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+        "-Wno-unused-parameter",
+    ],
+    sanitize: {
+        scs: true,
+    },
+}
+
+// Bluetooth APK
+
+android_app {
+    name: "Bluetooth",
+
+    srcs: [
+        "src/**/*.java",
+        ":statslog-bluetooth-java-gen",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+
+    jni_libs: ["libbluetooth_jni"],
+    libs: [
+        "javax.obex",
+        "services.net",
+    ],
+    static_libs: [
+        "com.android.vcard",
+        "bluetooth.mapsapi",
+        "sap-api-java-static",
+        "services.net",
+        "libprotobuf-java-lite",
+        "bluetooth-protos-lite",
+        "androidx.core_core",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.lifecycle_lifecycle-livedata",
+        "androidx.room_room-runtime",
+        "guava",
+    ],
+
+    plugins: [
+        "androidx.room_room-compiler-plugin",
+    ],
+
+    // Add in path to Bluetooth directory because local path does not exist
+    javacflags: ["-Aroom.schemaLocation=packages/apps/Bluetooth/tests/unit/src/com/android/bluetooth/btservice/storage/schemas"],
+
+    optimize: {
+        enabled: false,
+    },
+    required: ["libbluetooth"],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.bluetooth.updatable",
+    ],
+}
+
+genrule {
+    name: "statslog-bluetooth-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module bluetooth"
+        + " --javaPackage com.android.bluetooth --javaClass BluetoothStatsLog --worksource",
+    out: ["com/android/bluetooth/BluetoothStatsLog.java"],
+}
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 0f80f52..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,85 +0,0 @@
-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_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)
-LOCAL_PACKAGE_NAME := Bluetooth
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_CERTIFICATE := platform
-LOCAL_USE_AAPT2 := true
-LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
-LOCAL_JAVA_LIBRARIES := javax.obex telephony-common services.net
-LOCAL_STATIC_JAVA_LIBRARIES := \
-        com.android.vcard \
-        bluetooth.mapsapi \
-        sap-api-java-static \
-        services.net \
-        libprotobuf-java-lite \
-        bluetooth-protos-lite \
-        guava \
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-        androidx.core_core \
-        androidx.legacy_legacy-support-v4 \
-        androidx.lifecycle_lifecycle-livedata \
-        androidx.room_room-runtime \
-        bt-androidx-room-runtime-nodeps \
-
-LOCAL_ANNOTATION_PROCESSORS := \
-        bt-androidx-annotation-nodeps \
-        bt-androidx-room-common-nodeps \
-        bt-androidx-room-compiler-nodeps \
-        bt-androidx-room-migration-nodeps \
-        bt-antlr4-nodeps \
-        bt-apache-commons-codec-nodeps \
-        bt-auto-common-nodeps \
-        bt-javapoet-nodeps \
-        bt-kotlin-metadata-nodeps \
-        bt-sqlite-jdbc-nodeps \
-        bt-jetbrain-nodeps \
-        guava-21.0 \
-        kotlin-stdlib \
-        gson-prebuilt-jar
-
-LOCAL_JAVACFLAGS := \
-        -Aroom.schemaLocation=$(LOCAL_PATH)/tests/unit/src/com/android/bluetooth/btservice/storage/schemas
-
-LOCAL_ANNOTATION_PROCESSOR_CLASSES := \
-        androidx.room.RoomProcessor
-
-LOCAL_REQUIRED_MODULES := libbluetooth
-LOCAL_PROGUARD_ENABLED := disabled
-include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
-include $(CLEAR_VARS)
-
-COMMON_LIBS_PATH := ../../../../../prebuilts/tools/common/m2/repository
-ROOM_LIBS_PATH := ../../lib/room
-
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
-        bt-androidx-annotation-nodeps:$(ROOM_LIBS_PATH)/annotation-1.0.0-beta01.jar \
-        bt-androidx-room-common-nodeps:$(ROOM_LIBS_PATH)/room-common-2.0.0-beta01.jar \
-        bt-androidx-room-compiler-nodeps:$(ROOM_LIBS_PATH)/room-compiler-2.0.0-beta01.jar \
-        bt-androidx-room-migration-nodeps:$(ROOM_LIBS_PATH)/room-migration-2.0.0-beta01.jar \
-        bt-androidx-room-runtime-nodeps:$(ROOM_LIBS_PATH)/room-runtime-2.0.0-alpha1.aar \
-        bt-antlr4-nodeps:$(COMMON_LIBS_PATH)/org/antlr/antlr4/4.5.3/antlr4-4.5.3.jar \
-        bt-apache-commons-codec-nodeps:$(COMMON_LIBS_PATH)/org/eclipse/tycho/tycho-bundles-external/0.18.1/eclipse/plugins/org.apache.commons.codec_1.4.0.v201209201156.jar \
-        bt-auto-common-nodeps:$(COMMON_LIBS_PATH)/com/google/auto/auto-common/0.9/auto-common-0.9.jar \
-        bt-javapoet-nodeps:$(COMMON_LIBS_PATH)/com/squareup/javapoet/1.8.0/javapoet-1.8.0.jar \
-        bt-kotlin-metadata-nodeps:$(COMMON_LIBS_PATH)/me/eugeniomarletti/kotlin-metadata/1.2.1/kotlin-metadata-1.2.1.jar \
-        bt-sqlite-jdbc-nodeps:$(COMMON_LIBS_PATH)/org/xerial/sqlite-jdbc/3.20.1/sqlite-jdbc-3.20.1.jar \
-        bt-jetbrain-nodeps:../../../../../prebuilts/tools/common/m2/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar
-
-include $(BUILD_HOST_PREBUILT)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 92ecae6..5faf095 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,6 +34,7 @@
     <uses-permission android:name="android.permission.READ_CALL_LOG" />
     <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+    <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.NFC_HANDOVER_STATUS" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
@@ -45,6 +46,8 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.NETWORK_FACTORY" />
     <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
@@ -224,12 +227,10 @@
             android:excludeFromRecents="true"
             android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
             android:enabled="@bool/profile_supported_pbap">
-            <intent-filter>
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
         </activity>
         <service
             android:process="@string/process"
+            android:permission="android.permission.BLUETOOTH_PRIVILEGED"
             android:name=".pbap.BluetoothPbapService"
             android:enabled="@bool/profile_supported_pbap" >
             <intent-filter>
diff --git a/AndroidManifest_test.xml b/AndroidManifest_test.xml
deleted file mode 100644
index 8529ba5..0000000
--- a/AndroidManifest_test.xml
+++ /dev/null
@@ -1,106 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="com.android.bluetooth"
-  android:sharedUserId="@string/sharedUserId">
-    <!-- Allows access to the Bluetooth Share Manager -->
-    <permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE"
-        android:label="@string/permlab_bluetoothShareManager"
-        android:description="@string/permdesc_bluetoothShareManager"
-        android:protectionLevel="signature" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <application
-        android:icon="@mipmap/bt_share"
-        android:label="@string/app_name">
-        <uses-library android:name="javax.obex" />
-        <activity android:name=".opp.TestActivity" android:label="@string/app_name">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-        <provider android:name=".opp.BluetoothOppProvider"
-            android:authorities="com.android.bluetooth.opp"
-            android:process="@string/process">
-            <path-permission
-                    android:path="/btopp"
-                    android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
-        </provider>
-        <service android:process="@string/process"
-            android:name=".opp.BluetoothOppService"
-            android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
-        <receiver android:process="@string/process"
-              android:name=".opp.BluetoothOppReceiver">
-            <intent-filter>
-                <action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-        </receiver>
-        <activity android:name=".opp.BluetoothOppLauncherActivity"
-            android:process="@string/process"
-            android:theme="@*android:style/Theme.Material.DayNight.Dialog"
-            android:label="@string/bt_share_picker_label">
-            <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="image/*" />
-                <data android:mimeType="video/*" />
-                <data android:mimeType="audio/*" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.SEND_MULTIPLE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="image/*" />
-                <data android:mimeType="video/*" />
-                <data android:mimeType="x-mixmedia/*" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.btopp.intent.action.OPEN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="vnd.android.cursor.item/vnd.android.btopp" />
-            </intent-filter>
-        </activity>
-        <activity android:name=".opp.BluetoothOppBtEnableActivity"
-            android:process="@string/process">
-        </activity>
-        <activity android:name=".opp.BluetoothOppBtErrorActivity"
-          android:process="@string/process">
-        </activity>
-        <activity android:name=".opp.BluetoothOppBtEnablingActivity"
-            android:process="@string/process"
-            android:theme="@*android:style/Theme.Material.DayNight.Dialog">
-        </activity>
-        <activity android:name=".opp.BluetoothOppIncomingFileConfirmActivity"
-            android:process="@string/process">
-        </activity>
-        <activity android:name=".opp.BluetoothOppTransferActivity"
-            android:process="@string/process">
-        </activity>
-        <activity android:name=".pbap.BluetoothPbapActivity"
-            android:process="@string/process"
-            android:label=" "
-            android:theme="@*android:style/Theme.Material.DayNight.Dialog.Alert">
-            <intent-filter>
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-        <service android:process="@string/process"
-              android:name=".pbap.BluetoothPbapService" >
-            <action android:name="android.bluetooth.IBluetoothPbap"/>
-        </service>
-        <receiver android:process="@string/process"
-              android:name=".pbap.BluetoothPbapReceiver">
-            <intent-filter>
-                <action android:name="android.bluetooth.adapter.action.STATE_CHANGED"/>
-            </intent-filter>
-        </receiver>
-    </application>
-</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
deleted file mode 100644
index b84e1b6..0000000
--- a/CleanSpec.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-# 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.
-#
-
-# If you don't need to do a full clean build but would like to touch
-# a file or delete some intermediate files, add a clean step to the end
-# of the list.  These steps will only be run once, if they haven't been
-# run before.
-#
-# E.g.:
-#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
-#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
-#
-# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
-# files that are missing or have been moved.
-#
-# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
-# Use $(OUT_DIR) to refer to the "out" directory.
-#
-# If you need to re-do something that's already mentioned, just copy
-# the command and add it to the bottom of the list.  E.g., if a change
-# that you made last week required touching a file and a change you
-# made today requires touching the same file, just copy the old
-# touch step and add it to the end of the list.
-#
-# ************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
-# ************************************************
-
-# For example:
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
-#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
-#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
-
-# ************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
-# ************************************************
diff --git a/jni/Android.bp b/jni/Android.bp
deleted file mode 100644
index e0034b1..0000000
--- a/jni/Android.bp
+++ /dev/null
@@ -1,41 +0,0 @@
-cc_library_shared {
-    name: "libbluetooth_jni",
-    compile_multilib: "first",
-    srcs: [
-        "com_android_bluetooth_btservice_AdapterService.cpp",
-        "com_android_bluetooth_hfp.cpp",
-        "com_android_bluetooth_hfpclient.cpp",
-        "com_android_bluetooth_a2dp.cpp",
-        "com_android_bluetooth_a2dp_sink.cpp",
-        "com_android_bluetooth_avrcp_controller.cpp",
-        "com_android_bluetooth_avrcp_target.cpp",
-        "com_android_bluetooth_hid_host.cpp",
-        "com_android_bluetooth_hid_device.cpp",
-        "com_android_bluetooth_hearing_aid.cpp",
-        "com_android_bluetooth_pan.cpp",
-        "com_android_bluetooth_gatt.cpp",
-        "com_android_bluetooth_sdp.cpp",
-    ],
-    header_libs: ["libbluetooth_headers"],
-    include_dirs: [
-        "system/bt/types",
-    ],
-    shared_libs: [
-        "libbase",
-        "libchrome",
-        "libnativehelper",
-        "liblog",
-    ],
-    static_libs: [
-        "libbluetooth-types",
-    ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wextra",
-        "-Wno-unused-parameter",
-    ],
-    sanitize: {
-        scs: true,
-    },
-}
diff --git a/jni/com_android_bluetooth_a2dp.cpp b/jni/com_android_bluetooth_a2dp.cpp
index 7ca0aa5..3e155c7 100644
--- a/jni/com_android_bluetooth_a2dp.cpp
+++ b/jni/com_android_bluetooth_a2dp.cpp
@@ -259,7 +259,8 @@
 
 static void initNative(JNIEnv* env, jobject object,
                        jint maxConnectedAudioDevices,
-                       jobjectArray codecConfigArray) {
+                       jobjectArray codecConfigArray,
+                       jobjectArray codecOffloadingArray) {
   std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
   std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
 
@@ -305,8 +306,12 @@
   std::vector<btav_a2dp_codec_config_t> codec_priorities =
       prepareCodecPreferences(env, object, codecConfigArray);
 
+  std::vector<btav_a2dp_codec_config_t> codec_offloading =
+      prepareCodecPreferences(env, object, codecOffloadingArray);
+
   bt_status_t status = sBluetoothA2dpInterface->init(
-      &sBluetoothA2dpCallbacks, maxConnectedAudioDevices, codec_priorities);
+      &sBluetoothA2dpCallbacks, maxConnectedAudioDevices, codec_priorities,
+      codec_offloading);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("%s: Failed to initialize Bluetooth A2DP, status: %d", __func__,
           status);
@@ -474,7 +479,8 @@
 
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void*)classInitNative},
-    {"initNative", "(I[Landroid/bluetooth/BluetoothCodecConfig;)V",
+    {"initNative",
+     "(I[Landroid/bluetooth/BluetoothCodecConfig;[Landroid/bluetooth/BluetoothCodecConfig;)V",
      (void*)initNative},
     {"cleanupNative", "()V", (void*)cleanupNative},
     {"connectA2dpNative", "([B)Z", (void*)connectA2dpNative},
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
old mode 100644
new mode 100755
index 112e826..050da06
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -48,6 +48,7 @@
 static jmethodID method_handleSetAddressedPlayerRsp;
 static jmethodID method_handleAddressedPlayerChanged;
 static jmethodID method_handleNowPlayingContentChanged;
+static jmethodID method_onAvailablePlayerChanged;
 
 static jclass class_MediaBrowser_MediaItem;
 static jclass class_AvrcpPlayer;
@@ -719,6 +720,31 @@
       sCallbacksObj, method_handleNowPlayingContentChanged, addr.get());
 }
 
+static void btavrcp_available_player_changed_callback (
+    const RawAddress& bd_addr) {
+    ALOGI("%s", __func__);
+
+    std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
+
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbacksObj) {
+        ALOGE("%s: sCallbacksObj is null", __func__);
+        return;
+    }
+    if (!sCallbackEnv.valid()) return;
+    ScopedLocalRef<jbyteArray> addr(
+        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+    if (!addr.get()) {
+      ALOGE("%s: Failed to allocate a new byte array", __func__);
+      return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                     (jbyte*)&bd_addr);
+    sCallbackEnv->CallVoidMethod(
+        sCallbacksObj, method_onAvailablePlayerChanged, addr.get());
+}
+
 static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
     sizeof(sBluetoothAvrcpCallbacks),
     btavrcp_passthrough_response_callback,
@@ -738,7 +764,8 @@
     btavrcp_set_browsed_player_callback,
     btavrcp_set_addressed_player_callback,
     btavrcp_addressed_player_changed_callback,
-    btavrcp_now_playing_content_changed_callback};
+    btavrcp_now_playing_content_changed_callback,
+    btavrcp_available_player_changed_callback};
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
   method_handlePassthroughRsp =
@@ -804,6 +831,8 @@
       env->GetMethodID(clazz, "handleAddressedPlayerChanged", "([BI)V");
   method_handleNowPlayingContentChanged =
       env->GetMethodID(clazz, "handleNowPlayingContentChanged", "([B)V");
+  method_onAvailablePlayerChanged =
+      env->GetMethodID(clazz, "onAvailablePlayerChanged", "([B)V");
 
   ALOGI("%s: succeeds", __func__);
 }
diff --git a/lib/room/annotation-1.0.0-beta01.jar b/lib/room/annotation-1.0.0-beta01.jar
deleted file mode 100644
index 124f128..0000000
--- a/lib/room/annotation-1.0.0-beta01.jar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-common-2.0.0-beta01.jar b/lib/room/room-common-2.0.0-beta01.jar
deleted file mode 100644
index 931d4aa..0000000
--- a/lib/room/room-common-2.0.0-beta01.jar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-compiler-2.0.0-beta01.jar b/lib/room/room-compiler-2.0.0-beta01.jar
deleted file mode 100644
index 623a07b..0000000
--- a/lib/room/room-compiler-2.0.0-beta01.jar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-migration-2.0.0-beta01.jar b/lib/room/room-migration-2.0.0-beta01.jar
deleted file mode 100644
index 04bca6f..0000000
--- a/lib/room/room-migration-2.0.0-beta01.jar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-runtime-2.0.0-alpha1.aar b/lib/room/room-runtime-2.0.0-alpha1.aar
deleted file mode 100644
index 053188b..0000000
--- a/lib/room/room-runtime-2.0.0-alpha1.aar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-testing-2.0.0-alpha1.aar b/lib/room/room-testing-2.0.0-alpha1.aar
deleted file mode 100644
index bb6e62a..0000000
--- a/lib/room/room-testing-2.0.0-alpha1.aar
+++ /dev/null
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 060fd90..f011684 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Koppel aan Bluetooth"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index dbf4aad..0d2f87c 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ከብሉቱዝ ጋር ተገናኝ"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 1525a71..5e3127b 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -142,4 +142,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"الاتصال ببلوتوث"</string>
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index fd6ab37..a5f682c 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ব্লুটুথৰ সৈতে সংযোগ কৰক"</string>
 </resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 4abf91f..ea55d17 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth\'a qoşulun"</string>
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 4d999f3..9ad26e1 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -136,4 +136,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Poveži sa Bluetooth-om"</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 0026ea6..3c9dbd6 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -138,4 +138,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Падключыцца да Bluetooth"</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 1566a37..4b57342 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Свързване с Bluetooth"</string>
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 352f359..be01649 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ব্লুটুথের সাথে কানেক্ট করুন"</string>
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 7f6eca1..3c7dec4 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -136,4 +136,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Poveži se na Bluetooth"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 2c6ec73..bddf096 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Accediu al gestor de baixades."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permet que l\'aplicació accedeixi al gestor d\'ús compartit de Bluetooth i que l\'utilitzi per transferir fitxers."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Accés al dispositiu Bluetooth en llista blanca."</string>
+    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Accés de dispositius Bluetooth en llista blanca."</string>
     <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permet que l\'aplicació col·loqui temporalment en una llista blanca un dispositiu Bluetooth, cosa que permet que el dispositiu enviï fitxers a aquest dispositiu sense la confirmació de l\'usuari."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispositiu desconegut"</string>
@@ -30,7 +30,7 @@
     <string name="bt_enable_line2" msgid="4341936569415937994">"Vols activar el Bluetooth ara?"</string>
     <string name="bt_enable_cancel" msgid="1988832367505151727">"Cancel·la"</string>
     <string name="bt_enable_ok" msgid="3432462749994538265">"Activa"</string>
-    <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Transferència del fitxer"</string>
+    <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Transferència de fitxers"</string>
     <string name="incoming_file_confirm_content" msgid="2752605552743148036">"Acceptes el fitxer entrant?"</string>
     <string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Rebutja"</string>
     <string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accepta"</string>
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connecta el Bluetooth"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 3e76d2b..9adb80c 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -138,4 +138,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Připojit k Bluetooth"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 25819a1..25cf3d7 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Opret forbindelse til Bluetooth"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index c9e6554..4ffe470 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Mit Bluetooth verbinden"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 1506bd2..e249a38 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Σύνδεση σε Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index ddca497..8dcbab4 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index e549cb6..07202ef 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index ddca497..8dcbab4 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index ddca497..8dcbab4 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 02178d4..8bbc66c 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎Connect to Bluetooth‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 4a4eaaa..eb95310 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectarse a Bluetooth"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index a9e448c..a67ba6b 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -122,7 +122,7 @@
     <string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string>
     <string name="transfer_menu_clear" msgid="5854038118831427492">"Borrar de la lista"</string>
     <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Borrar"</string>
-    <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Está Sonando"</string>
+    <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Reproduciendo"</string>
     <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Guardar"</string>
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
     <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecciona las cuentas que quieras compartir por Bluetooth. Tendrás que aceptar cualquier acceso a las cuentas al establecer conexión."</string>
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectarse a un dispositivo Bluetooth"</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index dbc425a..334174f 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Ühenda Bluetoothiga"</string>
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index cc6e417..72e6096 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Konektatu Bluetooth-era"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index c95558e..e2c1b75 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"اتصال به بلوتوث"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 0fbc55c..f1792c6 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Muodosta Bluetooth-yhteys"</string>
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index b881cfc..4a62f98 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connexion au Bluetooth"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 06fe209..e0caaee 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Se connecter au Bluetooth"</string>
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 9dedefd..1f2b1d0 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectar ao Bluetooth"</string>
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index d6e1000..4195292 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"બ્લૂટૂથ ઑડિઓ ડિસ્કનેક્ટ થયું"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"બ્લૂટૂથ ઑડિઓ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB કરતા મોટી ફાઇલ ટ્રાન્સફર કરી શકાતી નથી"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"બ્લૂટૂથ સાથે કનેક્ટ કરો"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index d01d137..cb0bb5f 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ब्लूटूथ से कनेक्ट करें"</string>
 </resources>
diff --git a/res/values-hi/test_strings.xml b/res/values-hi/test_strings.xml
index f8379b8..52e0e1a 100644
--- a/res/values-hi/test_strings.xml
+++ b/res/values-hi/test_strings.xml
@@ -3,7 +3,7 @@
     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="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>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index d05c7b3..581e1a8 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -136,4 +136,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Povezivanje s Bluetoothom"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 4fa442f..ff93e44 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Csatlakozás Bluetooth-eszközhöz"</string>
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index bf6fbf3..1fb6d74 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Միանալ Bluetooth-ին"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 15722da..01c4fe1 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Hubungkan ke Bluetooth"</string>
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 6246389..65db6e9 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Tengjast við Bluetooth"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index e4a486f..27cefda 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connettiti a Bluetooth"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 7fe6a4f..11446ff 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -69,7 +69,7 @@
     <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>
@@ -138,4 +138,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"‏התחברות באמצעות Bluetooth"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 6171772..7688d48 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth に接続する"</string>
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 4983eaa..92b6329 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-თან დაკავშირება"</string>
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 82de8a6..811187f 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-ге қосылу"</string>
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index f4b7e7a..1db467c 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/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_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
+    <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"ឥឡូវកំពុងចាក់"</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>
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ភ្ជាប់​ប៊្លូធូស"</string>
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 0f0e06f..1d87823 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೊ ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೊ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ಗಿಂತ ದೊಡ್ಡದಾದ ಫೈಲ್‌ಗಳನ್ನು ವರ್ಗಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ಬ್ಲೂಟೂತ್‌ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index f5eeca9..3977271 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"블루투스 오디오가 연결 해제됨"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"블루투스 오디오"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB보다 큰 파일은 전송할 수 없습니다"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"블루투스에 연결"</string>
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 0ed4aa9..9af9bc8 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -125,7 +125,7 @@
     <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Азыр эмне ойноп жатат?"</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">"Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташкан сайын каттоо эсептерине кирүү мүмкүнчүлүгүн ырастап турушуңуз керек."</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_title" msgid="7420332483392851321">"Bluetooth билдирүү бөлүшүү жөндөөлөрү"</string>
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth\'га туташуу"</string>
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index bebe622..3257841 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ເຊື່ອມຕໍ່ກັບ Bluetooth"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 5d0f759..42562f4 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -138,4 +138,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Prisijungti prie „Bluetooth“"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 062a019..6edd984 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -136,4 +136,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Izveidot savienojumu ar Bluetooth"</string>
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 011c39d..5045b56 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Поврзи се со Bluetooth"</string>
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 08e907f..40e311f 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -132,4 +132,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-ലേക്ക് കണക്‌റ്റ് ചെയ്യുക"</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index aec448c..60f238a 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-тэй холбогдох"</string>
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index a7c0f1e..43e0a54 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -127,11 +127,12 @@
     <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_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">"4 GB हून मोठ्या फायली ट्रान्सफर करता येणार नाहीत"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ब्लूटूथशी कनेक्ट करा"</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index ab9c7a9..a0071ca 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Sambung ke Bluetooth"</string>
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 7e7763a..eb35b0f 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ဘလူးတုသ်အသံ မချိတ်ဆက်ထားပါ"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ဘလူးတုသ် အသံ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ထက်ပိုကြီးသည့် ဖိုင်များကို လွှဲပြောင်းမရနိုင်ပါ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ဘလူးတုသ်သို့ ချိတ်ဆက်ရန်"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index e0418b1..d6f9e77 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Koble til Bluetooth"</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 639df71..6f9ab58 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ब्लुटुथमा जोड्नुहोस्"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 9207378..8f49620 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Verbinding maken met bluetooth"</string>
 </resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 8819773..94ad041 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ ବିଚ୍ଛିନ୍ନ କରାଗଲା"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GBରୁ ବଡ଼ ଫାଇଲ୍‌ଗୁଡ଼ିକୁ ଟ୍ରାନ୍ସଫର୍‌ କରାଯାଇପାରିବ ନାହିଁ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ବ୍ଲୁଟୁଥ୍ ସହ ସଂଯୋଗ କରନ୍ତୁ"</string>
 </resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 07cab6a..71c771b 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ਬਲੂਟੁੱਥ  ਆਡੀਓ  ਡਿਸਕਨੈਕਟ ਕੀਤੀ ਗਈ"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ਬਲੂਟੁੱਥ  ਆਡੀਓ"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ਤੋਂ ਵਧੇਰੇ ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਟ੍ਰਾਂਸਫ਼ਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"ਬਲੂਟੁੱਥ ਨਾਲ ਕਨੈਕਟ ਕਰੋ"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 8c4869f..3cfbe0d 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -138,4 +138,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Nawiązywanie połączeń przez Bluetooth"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 09e6781..9eae5c9 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Ligar ao Bluetooth"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 8472432..b4a775e 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectar ao Bluetooth"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 67f117d..fadc4a6 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -136,4 +136,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectați-vă la Bluetooth"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 87c1492..6625b83 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -138,4 +138,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Подключиться по Bluetooth"</string>
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index ed6e355..bbe1e00 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"බ්ලූටූත් ශ්‍රව්‍යය විසන්ධි කරන ලදී"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"බ්ලූටූත් ශ්‍රව්‍යය"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GBට වඩා විශාල ගොනු මාරු කළ නොහැකිය"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"බ්ලූටූත් වෙත සබඳින්න"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index d189d0d..e28a792 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -138,4 +138,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Pripojiť k zariadeniu Bluetooth"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 912ba37..d681fb2 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -138,4 +138,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Povezovanje z Bluetoothom"</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 1a95dcd..5d1aff3 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Lidhu me Bluetooth"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 6bdf67a..25fc24f 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -136,4 +136,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Повежи са Bluetooth-ом"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 7c1609f..f243475 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Anslut till Bluetooth"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 39f803e..edc03d9 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Unganisha kwenye Bluetooth"</string>
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index e584f9f..67d872f 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"புளூடூத் உடன் இணை"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 7aaeaa4..e7f7775 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"బ్లూటూత్ ఆడియో డిస్‌కనెక్ట్ చేయబడింది"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"బ్లూటూత్ ఆడియో"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB కన్నా పెద్ద ఫైళ్లు బదిలీ చేయబడవు"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"బ్లూటూత్‌కు కనెక్ట్ చేయి"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 8cfec4f..48023cd 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"เชื่อมต่อบลูทูธ"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 1f50ceb..3820800 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Kumonekta sa Bluetooth"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 71ce9a4..e1a0bfb 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth\'a bağlan"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index e357d13..f7d6fbd 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -138,4 +138,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Підключитися до Bluetooth"</string>
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index cc13746..e944fb8 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"بلوٹوتھ آڈیو غیر منسلک ہے"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"بلوٹوتھ آڈیو"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"‏4GB سے بڑی فائلیں منتقل نہیں کی جا سکتیں"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"بلوٹوتھ سے منسلک کریں"</string>
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index dac670b..cadb8be 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetoothga ulanish"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 4a40dfe..e0653c0 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -128,10 +128,11 @@
     <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Chọn tài khoản mà bạn muốn chia sẻ qua Bluetooth. Bạn vẫn phải chấp nhận mọi quyền truy cập vào tài khoản khi kết nối."</string>
     <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Số khe cắm còn lại:"</string>
     <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Biểu tượng ứng dụng"</string>
-    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Cài đặt chia sẻ thư qua Bluetooth"</string>
+    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Cài đặt cách chia sẻ thư qua Bluetooth"</string>
     <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Không chọn được tài khoản. Còn lại 0 khe cắm"</string>
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Kết nối với Bluetooth"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 4898537..a5f6d2d 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"蓝牙音频已断开连接"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"蓝牙音频"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"无法传输 4GB 以上的文件"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"连接到蓝牙"</string>
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index dfb352a..2881ffe 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"連接藍牙"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 046dfc6..c8ece01 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -134,4 +134,5 @@
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"已中斷與藍牙音訊的連線"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"藍牙音訊"</string>
     <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"無法轉移大於 4GB 的檔案"</string>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"使用藍牙連線"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 1bc0fe6..da08ed2 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -134,4 +134,5 @@
     <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>
+    <string name="bluetooth_connect_action" msgid="4009848433321657090">"Xhumeka ku-Bluetooth"</string>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0204f98..5b2ad94 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -228,13 +228,13 @@
     <string name="transfer_clear_dlg_title">Clear</string>
 
      <!-- Do not translate. file name used for sharing. -->
-    <string name="bluetooth_share_file_name" translate="false">bluetooth_content_share</string>
+    <string name="bluetooth_share_file_name" translatable="false">bluetooth_content_share</string>
 
     <!-- Used to run Bluetooth.apk in another process if needed -->
     <!-- Do not translate. android:sharedUserId string of this application. -->
-    <string name="sharedUserId" translate="false"><xliff:g id="x" /></string>
+    <string name="sharedUserId" translatable="false"><xliff:g id="x" /></string>
     <!-- Do not translate. android:process of this application. -->
-    <string name="process" translate="false"><xliff:g id="x" /></string>
+    <string name="process" translatable="false"><xliff:g id="x" /></string>
 
 
     <string name="bluetooth_a2dp_sink_queue_name">Now Playing</string>
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index cb098ac..d86d289 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -16,14 +16,18 @@
 
 package com.android.bluetooth;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.location.LocationManager;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.ParcelUuid;
@@ -31,8 +35,12 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Telephony;
 import android.util.Log;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -58,6 +66,25 @@
     static final int BD_ADDR_LEN = 6; // bytes
     static final int BD_UUID_LEN = 16; // bytes
 
+    /*
+     * Special characters
+     *
+     * (See "What is a phone number?" doc)
+     * 'p' --- GSM pause character, same as comma
+     * 'n' --- GSM wild character
+     * 'w' --- GSM wait character
+     */
+    public static final char PAUSE = ',';
+    public static final char WAIT = ';';
+
+    private static boolean isPause(char c) {
+        return c == 'p' || c == 'P';
+    }
+
+    private static boolean isToneWait(char c) {
+        return c == 'w' || c == 'W';
+    }
+
     public static String getAddressStringFromByte(byte[] address) {
         if (address == null || address.length != BD_ADDR_LEN) {
             return null;
@@ -71,6 +98,10 @@
         return getBytesFromAddress(device.getAddress());
     }
 
+    public static byte[] addressToBytes(String address) {
+        return getBytesFromAddress(address);
+    }
+
     public static byte[] getBytesFromAddress(String address) {
         int i, j = 0;
         byte[] output = new byte[BD_ADDR_LEN];
@@ -254,6 +285,52 @@
         Utils.sForegroundUserId = uid;
     }
 
+    public static void enforceBluetoothPermission(Context context) {
+        context.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BLUETOOTH,
+                "Need BLUETOOTH permission");
+    }
+
+    public static void enforceBluetoothAdminPermission(Context context) {
+        context.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BLUETOOTH_ADMIN,
+                "Need BLUETOOTH ADMIN permission");
+    }
+
+    public static void enforceBluetoothPrivilegedPermission(Context context) {
+        context.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH PRIVILEGED permission");
+    }
+
+    public static void enforceLocalMacAddressPermission(Context context) {
+        context.enforceCallingOrSelfPermission(
+                android.Manifest.permission.LOCAL_MAC_ADDRESS,
+                "Need LOCAL_MAC_ADDRESS permission");
+    }
+
+    public static void enforceDumpPermission(Context context) {
+        context.enforceCallingOrSelfPermission(
+                android.Manifest.permission.DUMP,
+                "Need DUMP permission");
+    }
+
+    public static boolean callerIsSystemOrActiveUser(String tag, String method) {
+        if (!checkCaller()) {
+          Log.w(TAG, method + "() - Not allowed for non-active user and non-system user");
+          return false;
+        }
+        return true;
+    }
+
+    public static boolean callerIsSystemOrActiveOrManagedUser(Context context, String tag, String method) {
+        if (!checkCallerAllowManagedProfiles(context)) {
+          Log.w(TAG, method + "() - Not allowed for non-active user and non-system and non-managed user");
+          return false;
+        }
+        return true;
+    }
+
     public static boolean checkCaller() {
         int callingUser = UserHandle.getCallingUserId();
         int callingUid = Binder.getCallingUid();
@@ -311,7 +388,7 @@
      * OP_COARSE_LOCATION is allowed
      */
     public static boolean checkCallerHasCoarseLocation(Context context, AppOpsManager appOps,
-            String callingPackage, UserHandle userHandle) {
+            String callingPackage, @Nullable String callingFeatureId, UserHandle userHandle) {
         if (blockedByLocationOff(context, userHandle)) {
             Log.e(TAG, "Permission denial: Location is off.");
             return false;
@@ -321,7 +398,8 @@
         if (context.checkCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_COARSE_LOCATION)
                         == PackageManager.PERMISSION_GRANTED
-                && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+                && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
+                callingFeatureId)) {
             return true;
         }
 
@@ -336,7 +414,7 @@
      * OP_FINE_LOCATION is allowed
      */
     public static boolean checkCallerHasCoarseOrFineLocation(Context context, AppOpsManager appOps,
-            String callingPackage, UserHandle userHandle) {
+            String callingPackage, @Nullable String callingFeatureId, UserHandle userHandle) {
         if (blockedByLocationOff(context, userHandle)) {
             Log.e(TAG, "Permission denial: Location is off.");
             return false;
@@ -345,7 +423,8 @@
         if (context.checkCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_FINE_LOCATION)
                         == PackageManager.PERMISSION_GRANTED
-                && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+                && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
+                callingFeatureId)) {
             return true;
         }
 
@@ -353,7 +432,8 @@
         if (context.checkCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_COARSE_LOCATION)
                         == PackageManager.PERMISSION_GRANTED
-                && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+                && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
+                callingFeatureId)) {
             return true;
         }
 
@@ -367,7 +447,7 @@
      * OP_FINE_LOCATION is allowed
      */
     public static boolean checkCallerHasFineLocation(Context context, AppOpsManager appOps,
-            String callingPackage, UserHandle userHandle) {
+            String callingPackage, @Nullable String callingFeatureId, UserHandle userHandle) {
         if (blockedByLocationOff(context, userHandle)) {
             Log.e(TAG, "Permission denial: Location is off.");
             return false;
@@ -376,7 +456,8 @@
         if (context.checkCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_FINE_LOCATION)
                         == PackageManager.PERMISSION_GRANTED
-                && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+                && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
+                callingFeatureId)) {
             return true;
         }
 
@@ -412,7 +493,8 @@
         return true;
     }
 
-    private static boolean isAppOppAllowed(AppOpsManager appOps, int op, String callingPackage) {
+    private static boolean isAppOppAllowed(AppOpsManager appOps, String op, String callingPackage,
+            @NonNull String callingFeatureId) {
         return appOps.noteOp(op, Binder.getCallingUid(), callingPackage)
                 == AppOpsManager.MODE_ALLOWED;
     }
@@ -479,4 +561,65 @@
         return DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS")
                 .withZone(ZoneId.systemDefault()).format(Instant.now());
     }
+
+    public static void skipCurrentTag(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG
+                || parser.getDepth() > outerDepth)) {
+        }
+    }
+
+    /**
+     * Converts pause and tonewait pause characters
+     * to Android representation.
+     * RFC 3601 says pause is 'p' and tonewait is 'w'.
+     */
+    public static String convertPreDial(String phoneNumber) {
+        if (phoneNumber == null) {
+            return null;
+        }
+        int len = phoneNumber.length();
+        StringBuilder ret = new StringBuilder(len);
+
+        for (int i = 0; i < len; i++) {
+            char c = phoneNumber.charAt(i);
+
+            if (isPause(c)) {
+                c = PAUSE;
+            } else if (isToneWait(c)) {
+                c = WAIT;
+            }
+            ret.append(c);
+        }
+        return ret.toString();
+    }
+
+    /**
+     * Move a message to the given folder.
+     *
+     * @param context the context to use
+     * @param uri the message to move
+     * @param messageSent if the message is SENT or FAILED
+     * @return true if the operation succeeded
+     */
+    public static boolean moveMessageToFolder(Context context, Uri uri, boolean messageSent) {
+        if (uri == null) {
+            return false;
+        }
+
+        ContentValues values = new ContentValues(3);
+        if (messageSent) {
+            values.put(Telephony.Sms.READ, 1);
+            values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_SENT);
+        } else {
+            values.put(Telephony.Sms.READ, 0);
+            values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_FAILED);
+        }
+        values.put(Telephony.Sms.ERROR_CODE, 0);
+
+        return 1 == context.getContentResolver().update(uri, values, null, null);
+    }
 }
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 8127e76..6e0b1c0 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -17,11 +17,13 @@
 package com.android.bluetooth.a2dp;
 
 import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecConfig.CodecPriority;
 import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
+import android.media.AudioManager;
 import android.util.Log;
 
 import com.android.bluetooth.R;
@@ -39,22 +41,41 @@
     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;
+    private @CodecPriority int mA2dpSourceCodecPrioritySbc =
+            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    private @CodecPriority int mA2dpSourceCodecPriorityAac =
+            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    private @CodecPriority int mA2dpSourceCodecPriorityAptx =
+            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    private @CodecPriority int mA2dpSourceCodecPriorityAptxHd =
+            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+    private @CodecPriority int mA2dpSourceCodecPriorityLdac =
+            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+
+    private BluetoothCodecConfig[] mCodecConfigOffloading = new BluetoothCodecConfig[0];
 
     A2dpCodecConfig(Context context, A2dpNativeInterface a2dpNativeInterface) {
         mContext = context;
         mA2dpNativeInterface = a2dpNativeInterface;
         mCodecConfigPriorities = assignCodecConfigPriorities();
+
+        AudioManager audioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+        if (audioManager == null) {
+          Log.w(TAG, "Can't obtain the codec offloading prefernece from null AudioManager");
+          return;
+        }
+        mCodecConfigOffloading = audioManager.getHwOffloadEncodingFormatsSupportedForA2DP()
+                                             .toArray(mCodecConfigOffloading);
     }
 
     BluetoothCodecConfig[] codecConfigPriorities() {
         return mCodecConfigPriorities;
     }
 
+    BluetoothCodecConfig[] codecConfigOffloading() {
+        return mCodecConfigOffloading;
+    }
+
     void setCodecConfigPreference(BluetoothDevice device,
                                   BluetoothCodecStatus codecStatus,
                                   BluetoothCodecConfig newCodecConfig) {
diff --git a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
index cbdc28a..6202101 100644
--- a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
+++ b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
@@ -51,7 +51,7 @@
     private A2dpNativeInterface() {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         if (mAdapter == null) {
-            Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+            Log.wtf(TAG, "No Bluetooth Adapter Available");
         }
     }
 
@@ -75,8 +75,9 @@
      * @param codecConfigPriorities an array with the codec configuration
      * priorities to configure.
      */
-    public void init(int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities) {
-        initNative(maxConnectedAudioDevices, codecConfigPriorities);
+    public void init(int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities,
+            BluetoothCodecConfig[] codecConfigOffloading) {
+        initNative(maxConnectedAudioDevices, codecConfigPriorities, codecConfigOffloading);
     }
 
     /**
@@ -205,7 +206,8 @@
     // Native methods that call into the JNI interface
     private static native void classInitNative();
     private native void initNative(int maxConnectedAudioDevices,
-                                   BluetoothCodecConfig[] codecConfigPriorities);
+                                   BluetoothCodecConfig[] codecConfigPriorities,
+                                   BluetoothCodecConfig[] codecConfigOffloading);
     private native void cleanupNative();
     private native boolean connectA2dpNative(byte[] address);
     private native boolean disconnectA2dpNative(byte[] address);
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 167fc10..6dd2478 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -17,6 +17,8 @@
 package com.android.bluetooth.a2dp;
 
 import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus;
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
@@ -30,9 +32,9 @@
 import android.media.AudioManager;
 import android.os.HandlerThread;
 import android.util.Log;
-import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
@@ -40,6 +42,7 @@
 import com.android.bluetooth.btservice.ServiceFactory;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -72,6 +75,9 @@
     private final ConcurrentMap<BluetoothDevice, A2dpStateMachine> mStateMachines =
             new ConcurrentHashMap<>();
 
+    // Protect setActiveDevice() so all invoked is handled squentially
+    private final Object mActiveSwitchingGuard = new Object();
+
     // 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
@@ -123,7 +129,8 @@
 
         // Step 5: Initialize native interface
         mA2dpNativeInterface.init(mMaxConnectedAudioDevices,
-                                  mA2dpCodecConfig.codecConfigPriorities());
+                                  mA2dpCodecConfig.codecConfigPriorities(),
+                                  mA2dpCodecConfig.codecConfigOffloading());
 
         // Step 6: Check if A2DP is in offload mode
         mA2dpOffloadEnabled = mAdapterService.isA2dpOffloadEnabled();
@@ -229,12 +236,12 @@
             Log.d(TAG, "connect(): " + device);
         }
 
-        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
-            Log.e(TAG, "Cannot connect to " + device + " : PRIORITY_OFF");
+        if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN");
             return false;
         }
-        if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
-                                         BluetoothUuid.AudioSink)) {
+        if (!ArrayUtils.contains(mAdapterService.getRemoteUuids(device),
+                                         BluetoothUuid.A2DP_SINK)) {
             Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Sink UUID");
             return false;
         }
@@ -356,19 +363,18 @@
                     + " : too many connected devices");
             return false;
         }
-        // Check priority and accept or reject the connection.
-        int priority = getPriority(device);
+        // Check connectionPolicy and accept or reject the connection.
+        int connectionPolicy = getConnectionPolicy(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);
+        } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+                && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            // Otherwise, reject the connection if connectionPolicy is not valid.
+            Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
             return false;
         }
         return true;
@@ -386,8 +392,8 @@
         }
         synchronized (mStateMachines) {
             for (BluetoothDevice device : bondedDevices) {
-                if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
-                                                 BluetoothUuid.AudioSink)) {
+                if (!ArrayUtils.contains(mAdapterService.getRemoteUuids(device),
+                                                 BluetoothUuid.A2DP_SINK)) {
                     continue;
                 }
                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
@@ -434,16 +440,16 @@
     }
 
     private void removeActiveDevice(boolean forceStopPlayingAudio) {
-        BluetoothDevice previousActiveDevice = mActiveDevice;
-        synchronized (mStateMachines) {
+        synchronized (mActiveSwitchingGuard) {
+            BluetoothDevice previousActiveDevice = null;
+            synchronized (mStateMachines) {
+                if (mActiveDevice == null) return;
+                previousActiveDevice = mActiveDevice;
+            }
             // This needs to happen before we inform the audio manager that the device
             // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
             updateAndBroadcastActiveDevice(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
@@ -456,10 +462,13 @@
             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");
+
+            synchronized (mStateMachines) {
+                // 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");
+                }
             }
         }
     }
@@ -497,78 +506,94 @@
      */
     public boolean setActiveDevice(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-        synchronized (mStateMachines) {
-            BluetoothDevice previousActiveDevice = mActiveDevice;
-            if (DBG) {
-                Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
-            }
-
+        synchronized (mActiveSwitchingGuard) {
             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;
+            A2dpStateMachine sm = null;
+            BluetoothDevice previousActiveDevice = null;
+            synchronized (mStateMachines) {
+                if (Objects.equals(device, mActiveDevice)) {
+                    Log.i(TAG, "setActiveDevice(" + device + "): current is " + mActiveDevice
+                            + " no changed");
+                    // returns true since the device is activated even double attempted
+                    return true;
+                }
+                if (DBG) {
+                    Log.d(TAG, "setActiveDevice(" + device + "): current is " + mActiveDevice);
+                }
+                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;
+                }
+                previousActiveDevice = mActiveDevice;
             }
-            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);
+            // Switch from one A2DP to another A2DP device
+            if (DBG) {
+                Log.d(TAG, "Switch A2DP devices to " + device + " from " + previousActiveDevice);
+            }
             // This needs to happen before we inform the audio manager that the device
             // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
             updateAndBroadcastActiveDevice(device);
-            if (deviceChanged) {
+            // 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, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+                    wasMuted = true;
+                }
+                mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                        previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
+                        BluetoothProfile.A2DP, true, -1);
+            }
+
+            BluetoothDevice newActiveDevice = null;
+            synchronized (mStateMachines) {
+                if (!mA2dpNativeInterface.setActiveDevice(device)) {
+                    Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native "
+                            + "layer");
+                    // Remove active device and stop playing audio.
+                    removeActiveDevice(true);
+                    return false;
+                }
                 // Send an intent with the active device codec config
+                BluetoothCodecStatus codecStatus = sm.getCodecStatus();
                 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, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
-                        wasMuted = true;
-                    }
-                    mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                            previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
-                            BluetoothProfile.A2DP, true, -1);
-                }
+                newActiveDevice = mActiveDevice;
+            }
 
-                int rememberedVolume = -1;
-                if (mFactory.getAvrcpTargetService() != null) {
-                    rememberedVolume = mFactory.getAvrcpTargetService()
-                            .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, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
-                }
+            // Tasks of Bluetooth are done, and now restore the AudioManager side.
+            int rememberedVolume = -1;
+            if (mFactory.getAvrcpTargetService() != null) {
+                rememberedVolume = mFactory.getAvrcpTargetService()
+                        .getRememberedVolumeForDevice(newActiveDevice);
+            }
+            mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+                    newActiveDevice, 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(newActiveDevice);
+            if (wasMuted) {
+                mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+                        AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
             }
         }
         return true;
@@ -592,20 +617,54 @@
         }
     }
 
-    public boolean setPriority(BluetoothDevice device, int priority) {
+    /**
+     * Set connection policy of the profile and connects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         if (DBG) {
-            Log.d(TAG, "Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        mAdapterService.getDatabase()
-                .setProfilePriority(device, BluetoothProfile.A2DP, priority);
-        return true;
+        boolean setSuccessfully;
+        setSuccessfully = mAdapterService.getDatabase()
+                .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, connectionPolicy);
+        if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (setSuccessfully
+                && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
+        return setSuccessfully;
     }
 
-    public int getPriority(BluetoothDevice device) {
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         return mAdapterService.getDatabase()
-                .getProfilePriority(device, BluetoothProfile.A2DP);
+                .getProfileConnectionPolicy(device, BluetoothProfile.A2DP);
     }
 
     public boolean isAvrcpAbsoluteVolumeSupported() {
@@ -675,7 +734,8 @@
      */
     public void setCodecConfigPreference(BluetoothDevice device,
                                          BluetoothCodecConfig codecConfig) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
         if (DBG) {
             Log.d(TAG, "setCodecConfigPreference(" + device + "): "
                     + Objects.toString(codecConfig));
@@ -707,7 +767,8 @@
      * @hide
      */
     public void enableOptionalCodecs(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
         if (DBG) {
             Log.d(TAG, "enableOptionalCodecs(" + device + ")");
         }
@@ -738,7 +799,8 @@
      * @hide
      */
     public void disableOptionalCodecs(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
         if (DBG) {
             Log.d(TAG, "disableOptionalCodecs(" + device + ")");
         }
@@ -761,25 +823,55 @@
         mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig());
     }
 
-    public int getSupportsOptionalCodecs(BluetoothDevice device) {
+    /**
+     * Checks whether optional codecs are supported
+     *
+     * @param device is the remote bluetooth device.
+     * @return whether optional codecs are supported. Possible values are:
+     * {@link OptionalCodecsSupportStatus#OPTIONAL_CODECS_SUPPORTED},
+     * {@link OptionalCodecsSupportStatus#OPTIONAL_CODECS_NOT_SUPPORTED},
+     * {@link OptionalCodecsSupportStatus#OPTIONAL_CODECS_SUPPORT_UNKNOWN}.
+     */
+    public @OptionalCodecsSupportStatus int getSupportsOptionalCodecs(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         return mAdapterService.getDatabase().getA2dpSupportsOptionalCodecs(device);
     }
 
     public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
         int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED
                 : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
         mAdapterService.getDatabase().setA2dpSupportsOptionalCodecs(device, value);
     }
 
-    public int getOptionalCodecsEnabled(BluetoothDevice device) {
+    /**
+     * Checks whether optional codecs are enabled
+     *
+     * @param device is the remote bluetooth device
+     * @return whether the optional codecs are enabled. Possible values are:
+     * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_ENABLED},
+     * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_DISABLED},
+     * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_UNKNOWN}.
+     */
+    public @OptionalCodecsPreferenceStatus int getOptionalCodecsEnabled(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         return mAdapterService.getDatabase().getA2dpOptionalCodecsEnabled(device);
     }
 
-    public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+    /**
+     * Sets the optional codecs to be set to the passed in value
+     *
+     * @param device is the remote bluetooth device
+     * @param value is the new status for the optional codecs. Possible values are:
+     * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_ENABLED},
+     * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_DISABLED},
+     * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_UNKNOWN}.
+     */
+    public void setOptionalCodecsEnabled(BluetoothDevice device,
+            @OptionalCodecsPreferenceStatus int value) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
         if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
@@ -835,7 +927,7 @@
                             boolean sameAudioFeedingParameters) {
         // Log codec config and capability metrics
         BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig();
-        StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CONFIG_CHANGED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_A2DP_CODEC_CONFIG_CHANGED,
                 mAdapterService.obfuscateAddress(device), codecConfig.getCodecType(),
                 codecConfig.getCodecPriority(), codecConfig.getSampleRate(),
                 codecConfig.getBitsPerSample(), codecConfig.getChannelMode(),
@@ -843,7 +935,7 @@
                 codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4());
         BluetoothCodecConfig[] codecCapabilities = codecStatus.getCodecsSelectableCapabilities();
         for (BluetoothCodecConfig codecCapability : codecCapabilities) {
-            StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CAPABILITY_CHANGED,
+            BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_A2DP_CODEC_CAPABILITY_CHANGED,
                     mAdapterService.obfuscateAddress(device), codecCapability.getCodecType(),
                     codecCapability.getCodecPriority(), codecCapability.getSampleRate(),
                     codecCapability.getBitsPerSample(), codecCapability.getChannelMode(),
@@ -895,16 +987,16 @@
             Log.d(TAG, "updateAndBroadcastActiveDevice(" + device + ")");
         }
 
+        // Make sure volume has been store before device been remove from active.
+        if (mFactory.getAvrcpTargetService() != null) {
+            mFactory.getAvrcpTargetService().volumeDeviceSwitched(device);
+        }
         synchronized (mStateMachines) {
-            if (mFactory.getAvrcpTargetService() != null) {
-                mFactory.getAvrcpTargetService().volumeDeviceSwitched(device);
-            }
-
             mActiveDevice = device;
         }
 
-        StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.A2DP,
-                mAdapterService.obfuscateAddress(device));
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED,
+                BluetoothProfile.A2DP, mAdapterService.obfuscateAddress(device));
         Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -964,12 +1056,11 @@
             if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
                 return;
             }
-            if (mFactory.getAvrcpTargetService() != null) {
-                mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
-            }
-
-            removeStateMachine(device);
         }
+        if (mFactory.getAvrcpTargetService() != null) {
+            mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
+        }
+        removeStateMachine(device);
     }
 
     private void removeStateMachine(BluetoothDevice device) {
@@ -1049,28 +1140,24 @@
         if ((device == null) || (fromState == toState)) {
             return;
         }
-        synchronized (mStateMachines) {
-            if (toState == BluetoothProfile.STATE_CONNECTED) {
-                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) {
-                    if (mFactory.getAvrcpTargetService() != null) {
-                        mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
-                    }
-
-                    removeStateMachine(device);
+        if (toState == BluetoothProfile.STATE_CONNECTED) {
+            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) {
+            if (mAdapterService.getBondState(device) == BluetoothDevice.BOND_NONE) {
+                if (mFactory.getAvrcpTargetService() != null) {
+                    mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
                 }
+                removeStateMachine(device);
             }
         }
     }
@@ -1189,21 +1276,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             A2dpService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             A2dpService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
 
         @Override
@@ -1299,6 +1386,23 @@
     public void dump(StringBuilder sb) {
         super.dump(sb);
         ProfileService.println(sb, "mActiveDevice: " + mActiveDevice);
+        ProfileService.println(sb, "mMaxConnectedAudioDevices: " + mMaxConnectedAudioDevices);
+        if (mA2dpCodecConfig != null) {
+            ProfileService.println(sb, "codecConfigPriorities:");
+            for (BluetoothCodecConfig codecConfig : mA2dpCodecConfig.codecConfigPriorities()) {
+                ProfileService.println(sb, "  " + codecConfig.getCodecName() + ": "
+                        + codecConfig.getCodecPriority());
+            }
+            ProfileService.println(sb, "mA2dpOffloadEnabled: " + mA2dpOffloadEnabled);
+            if (mA2dpOffloadEnabled) {
+                ProfileService.println(sb, "codecConfigOffloading:");
+                for (BluetoothCodecConfig codecConfig : mA2dpCodecConfig.codecConfigOffloading()) {
+                    ProfileService.println(sb, "  " + codecConfig);
+                }
+            }
+        } else {
+            ProfileService.println(sb, "mA2dpCodecConfig: null");
+        }
         for (A2dpStateMachine sm : mStateMachines.values()) {
             sm.dump(sb);
         }
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index e0df8b2..946b9b4 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -54,16 +54,17 @@
 import android.os.Looper;
 import android.os.Message;
 import android.util.Log;
-import android.util.StatsLog;
 
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 import com.android.internal.annotations.VisibleForTesting;
-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.Objects;
 import java.util.Scanner;
 
 final class A2dpStateMachine extends StateMachine {
@@ -199,7 +200,7 @@
                     A2dpStackEvent event = (A2dpStackEvent) message.obj;
                     log("Disconnected: stack event: " + event);
                     if (!mDevice.equals(event.device)) {
-                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                        Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
                     }
                     switch (event.type) {
                         case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -306,7 +307,7 @@
                     A2dpStackEvent event = (A2dpStackEvent) message.obj;
                     log("Connecting: stack event: " + event);
                     if (!mDevice.equals(event.device)) {
-                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                        Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
                     }
                     switch (event.type) {
                         case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -399,7 +400,7 @@
                     A2dpStackEvent event = (A2dpStackEvent) message.obj;
                     log("Disconnecting: stack event: " + event);
                     if (!mDevice.equals(event.device)) {
-                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                        Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
                     }
                     switch (event.type) {
                         case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -509,7 +510,7 @@
                     A2dpStackEvent event = (A2dpStackEvent) message.obj;
                     log("Connected: stack event: " + event);
                     if (!mDevice.equals(event.device)) {
-                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                        Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
                     }
                     switch (event.type) {
                         case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -596,7 +597,7 @@
 
     boolean isConnected() {
         synchronized (this) {
-            return (getCurrentState() == mConnected);
+            return (getConnectionState() == BluetoothProfile.STATE_CONNECTED);
         }
     }
 
@@ -687,7 +688,7 @@
     private void broadcastAudioState(int newState, int prevState) {
         log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState)
                 + "->" + audioStateToString(newState));
-        StatsLog.write(StatsLog.BLUETOOTH_A2DP_PLAYBACK_STATE_CHANGED, newState);
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_A2DP_PLAYBACK_STATE_CHANGED, newState);
         Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
@@ -765,14 +766,27 @@
     }
 
     public void dump(StringBuilder sb) {
-        ProfileService.println(sb, "mDevice: " + mDevice);
-        ProfileService.println(sb, "  StateMachine: " + this.toString());
+        boolean isActive = Objects.equals(mDevice, mA2dpService.getActiveDevice());
+        ProfileService.println(sb,
+                "=== A2dpStateMachine for " + mDevice + (isActive ? " (Active) ===" : " ==="));
+        ProfileService.println(sb,
+                "  getConnectionPolicy: " + mA2dpService.getConnectionPolicy(mDevice));
+        ProfileService.println(sb, "  mConnectionState: " + profileStateToString(mConnectionState)
+                + ", mLastConnectionState: " + profileStateToString(mLastConnectionState));
         ProfileService.println(sb, "  mIsPlaying: " + mIsPlaying);
+        ProfileService.println(sb,
+                "  getSupportsOptionalCodecs: " + mA2dpService.getSupportsOptionalCodecs(mDevice)
+                + ", getOptionalCodecsEnabled: " + mA2dpService.getOptionalCodecsEnabled(mDevice));
         synchronized (this) {
             if (mCodecStatus != null) {
                 ProfileService.println(sb, "  mCodecConfig: " + mCodecStatus.getCodecConfig());
+                ProfileService.println(sb, "  mCodecsSelectableCapabilities:");
+                for (BluetoothCodecConfig config : mCodecStatus.getCodecsSelectableCapabilities()) {
+                    ProfileService.println(sb, "    " + config);
+                }
             }
         }
+        ProfileService.println(sb, "  StateMachine: " + this.toString());
         // Dump the state machine logs
         StringWriter stringWriter = new StringWriter();
         PrintWriter printWriter = new PrintWriter(stringWriter);
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 5271cc7..91a5774 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -171,21 +171,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             A2dpSinkService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             A2dpSinkService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
 
         @Override
@@ -224,8 +224,9 @@
             Log.d(TAG, " connect device: " + device
                     + ", InstanceMap start state: " + sb.toString());
         }
-        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
-            Log.w(TAG, "Connection not allowed: <" + device.getAddress() + "> is PRIORITY_OFF");
+        if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            Log.w(TAG, "Connection not allowed: <" + device.getAddress()
+                    + "> is CONNECTION_POLICY_FORBIDDEN");
             return false;
         }
         A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(device);
@@ -312,32 +313,47 @@
     }
 
     /**
-     * Set the priority of the  profile.
+     * Set connection policy of the profile and connects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
      *
-     * @param device   the remote device
-     * @param priority the priority of the profile
-     * @return true on success, otherwise false
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
      */
-    public boolean setPriority(BluetoothDevice device, int priority) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+        enforceCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
         if (DBG) {
-            Log.d(TAG, "Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
         AdapterService.getAdapterService().getDatabase()
-                .setProfilePriority(device, BluetoothProfile.A2DP_SINK, priority);
+                .setProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK, connectionPolicy);
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
         return true;
     }
 
     /**
-     * Get the priority of the profile.
+     * Get the connection policy of the profile.
      *
      * @param device the remote device
-     * @return priority of the specified device
+     * @return connection policy of the specified device
      */
-    public int getPriority(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+    public int getConnectionPolicy(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
         return AdapterService.getAdapterService().getDatabase()
-                .getProfilePriority(device, BluetoothProfile.A2DP_SINK);
+                .getProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK);
     }
 
 
@@ -362,7 +378,8 @@
     }
 
     boolean isA2dpPlaying(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        enforceCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
         return mA2dpSinkStreamHandler.isPlaying();
     }
 
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index 77ead16..de47a45 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -15,8 +15,6 @@
  */
 package com.android.bluetooth.a2dpsink;
 
-import static android.bluetooth.BluetoothProfile.PRIORITY_OFF;
-
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
@@ -30,8 +28,8 @@
 import com.android.bluetooth.Utils;
 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 com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 
 
 public class A2dpSinkStateMachine extends StateMachine {
@@ -171,7 +169,8 @@
                 case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
                     switch (event.mState) {
                         case StackEvent.CONNECTION_STATE_CONNECTING:
-                            if (mService.getPriority(mDevice) == PRIORITY_OFF) {
+                            if (mService.getConnectionPolicy(mDevice)
+                                    == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
                                 Log.w(TAG, "Ignore incoming connection, profile is"
                                         + " turned off for " + mDevice);
                                 mService.disconnectA2dpNative(mDeviceAddress);
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index ffd648a..5aa3cbb 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -183,8 +183,9 @@
                 break;
 
             case AUDIO_FOCUS_CHANGE:
+                mAudioFocus = (int) message.obj;
                 // message.obj is the newly granted audio focus.
-                switch ((int) message.obj) {
+                switch (mAudioFocus) {
                     case AudioManager.AUDIOFOCUS_GAIN:
                         removeMessages(DELAYED_PAUSE);
                         // Begin playing audio, if we paused the remote, send a play now.
@@ -245,12 +246,9 @@
      */
     private void requestAudioFocusIfNone() {
         if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
-        if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
+        if (mAudioFocus != AudioManager.AUDIOFOCUS_GAIN) {
             requestAudioFocus();
         }
-        // On the off change mMediaPlayer errors out and dies, we want to make sure we retry this.
-        // This function immediately exits if we have a MediaPlayer object.
-        requestMediaKeyFocus();
     }
 
     private synchronized int requestAudioFocus() {
@@ -277,8 +275,11 @@
     }
 
     /**
-     * Creates a MediaPlayer that plays a silent audio sample so that MediaSessionService will be
-     * aware of the fact that Bluetooth is playing audio.
+     * Plays a silent audio sample so that MediaSessionService will be aware of the fact that
+     * Bluetooth is playing audio.
+     *
+     * Creates a new MediaPlayer if one does not already exist. Repeat calls to this function are
+     * safe and will result in the silent audio sample again.
      *
      * This allows the MediaSession in AVRCP Controller to be routed media key events, if we've
      * chosen to use it.
@@ -286,25 +287,25 @@
     private synchronized void requestMediaKeyFocus() {
         if (DBG) Log.d(TAG, "requestMediaKeyFocus()");
 
-        if (mMediaPlayer != null) return;
-
-        AudioAttributes attrs = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA)
-                .build();
-
-        mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
-                mAudioManager.generateAudioSessionId());
         if (mMediaPlayer == null) {
-            Log.e(TAG, "Failed to initialize media player. You may not get media key events");
-            return;
-        }
+            AudioAttributes attrs = new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_MEDIA)
+                    .build();
 
-        mMediaPlayer.setLooping(false);
-        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
-            Log.e(TAG, "Silent media player error: " + what + ", " + extra);
-            releaseMediaKeyFocus();
-            return false;
-        });
+            mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
+                    mAudioManager.generateAudioSessionId());
+            if (mMediaPlayer == null) {
+                Log.e(TAG, "Failed to initialize media player. You may not get media key events");
+                return;
+            }
+
+            mMediaPlayer.setLooping(false);
+            mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+                Log.e(TAG, "Silent media player error: " + what + ", " + extra);
+                releaseMediaKeyFocus();
+                return false;
+            });
+        }
 
         mMediaPlayer.start();
         BluetoothMediaBrowserService.setActive(true);
@@ -313,7 +314,6 @@
     private synchronized void abandonAudioFocus() {
         if (DBG) Log.d(TAG, "abandonAudioFocus()");
         stopFluorideStreaming();
-        releaseMediaKeyFocus();
         mAudioManager.abandonAudioFocus(mAudioFocusListener);
         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
     }
@@ -336,9 +336,11 @@
     private void startFluorideStreaming() {
         mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
         mA2dpSinkService.informAudioTrackGainNative(1.0f);
+        requestMediaKeyFocus();
     }
 
     private void stopFluorideStreaming() {
+        releaseMediaKeyFocus();
         mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
     }
 
diff --git a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
index 1a2bba7..fc35fc1 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -114,6 +114,17 @@
                         Log.d(TAG, "request to disconnect device " + device);
                     }
                 }
+            } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+                if (streamType == AudioManager.STREAM_MUSIC) {
+                    int volume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
+                    BluetoothDevice activeDevice = getA2dpActiveDevice();
+                    if (activeDevice != null
+                            && !mVolumeManager.getAbsoluteVolumeSupported(activeDevice)) {
+                        Log.d(TAG, "stream volume change to " + volume + " " + activeDevice);
+                        mVolumeManager.storeVolumeForDevice(activeDevice, volume);
+                    }
+                }
             }
         }
     }
@@ -153,7 +164,7 @@
     @Override
     protected boolean start() {
         if (sInstance != null) {
-            Log.wtfStack(TAG, "The service has already been initialized");
+            Log.wtf(TAG, "The service has already been initialized");
             return false;
         }
 
@@ -185,6 +196,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
         registerReceiver(mReceiver, filter);
 
         // Only allow the service to be used once it is initialized
@@ -219,6 +231,23 @@
     private void init() {
     }
 
+    private BluetoothDevice getA2dpActiveDevice() {
+        A2dpService service = mFactory.getA2dpService();
+        if (service == null) {
+            return null;
+        }
+        return service.getActiveDevice();
+    }
+
+    private void setA2dpActiveDevice(BluetoothDevice device) {
+        A2dpService service = A2dpService.getA2dpService();
+        if (service == null) {
+            Log.d(TAG, "setA2dpActiveDevice: A2dp service not found");
+            return;
+        }
+        service.setActiveDevice(device);
+    }
+
     void deviceConnected(BluetoothDevice device, boolean absoluteVolume) {
         Log.i(TAG, "deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);
         mVolumeManager.deviceConnected(device, absoluteVolume);
@@ -263,7 +292,7 @@
 
     // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly.
     void setVolume(int avrcpVolume) {
-        BluetoothDevice activeDevice = mFactory.getA2dpService().getActiveDevice();
+        BluetoothDevice activeDevice = getA2dpActiveDevice();
         if (activeDevice == null) {
             Log.d(TAG, "setVolume: no active device");
             return;
@@ -277,7 +306,7 @@
      * volume.
      */
     public void sendVolumeChanged(int deviceVolume) {
-        BluetoothDevice activeDevice = mFactory.getA2dpService().getActiveDevice();
+        BluetoothDevice activeDevice = getA2dpActiveDevice();
         if (activeDevice == null) {
             Log.d(TAG, "sendVolumeChanged: no active device");
             return;
@@ -343,9 +372,9 @@
     void setActiveDevice(BluetoothDevice device) {
         Log.i(TAG, "setActiveDevice: device=" + device);
         if (device == null) {
-            Log.wtfStack(TAG, "setActiveDevice: could not find device " + device);
+            Log.wtf(TAG, "setActiveDevice: could not find device " + device);
         }
-        A2dpService.getA2dpService().setActiveDevice(device);
+        setA2dpActiveDevice(device);
     }
 
     /**
diff --git a/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
index c1369f8..e48d428 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
@@ -115,12 +115,11 @@
         volumeMapEditor.apply();
     }
 
-    synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device) {
+    synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device, int storeVolume) {
         if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
             return;
         }
         SharedPreferences.Editor pref = getVolumeMap().edit();
-        int storeVolume =  mAudioManager.getStreamVolume(STREAM_MUSIC);
         Log.i(TAG, "storeVolume: Storing stream volume level for device " + device
                 + " : " + storeVolume);
         mVolumeMap.put(device, storeVolume);
@@ -130,6 +129,11 @@
         pref.apply();
     }
 
+    synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device) {
+        int storeVolume =  mAudioManager.getStreamVolume(STREAM_MUSIC);
+        storeVolumeForDevice(device, storeVolume);
+    }
+
     synchronized void removeStoredVolumeForDevice(@NonNull BluetoothDevice device) {
         if (device.getBondState() != BluetoothDevice.BOND_NONE) {
             return;
@@ -183,6 +187,17 @@
         storeVolumeForDevice(device);
     }
 
+    /**
+     * True if remote device supported Absolute volume, false if remote device is not supported or
+     * not connected.
+     */
+    boolean getAbsoluteVolumeSupported(BluetoothDevice device) {
+        if (mDeviceMap.containsKey(device)) {
+            return mDeviceMap.get(device);
+        }
+        return false;
+    }
+
     @Override
     public synchronized void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
         if (mCurrentDevice == null) {
diff --git a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
index eafe3b9..e009f28 100644
--- a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
+++ b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
@@ -76,7 +76,7 @@
             return sInjectConnector;
         }
         if (cb == null) {
-            Log.wtfStack(TAG, "Null callback passed");
+            Log.wtf(TAG, "Null callback passed");
             return null;
         }
 
diff --git a/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java b/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
index 783da64..6ffdac5 100644
--- a/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
@@ -136,7 +136,7 @@
      */
     boolean connect(ConnectionCallback cb) {
         if (cb == null) {
-            Log.wtfStack(TAG, "connect: Trying to connect to " + mPackageName
+            Log.wtf(TAG, "connect: Trying to connect to " + mPackageName
                     + "with null callback");
         }
         return setCallbackAndConnect((int status, BrowsedPlayerWrapper wrapper) -> {
@@ -245,7 +245,7 @@
         }
 
         if (cb == null) {
-            Log.wtfStack(TAG, "getFolderItems: Trying to connect to " + mPackageName
+            Log.wtf(TAG, "getFolderItems: Trying to connect to " + mPackageName
                     + "with null browse callback");
         }
 
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerList.java b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
index 6fc8138..e7cb96a 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerList.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
@@ -18,12 +18,12 @@
 
 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.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
 import android.media.session.MediaSession;
@@ -31,6 +31,7 @@
 import android.media.session.PlaybackState;
 import android.os.Handler;
 import android.os.Looper;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 
@@ -137,7 +138,8 @@
                 (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
         mMediaSessionManager.addOnActiveSessionsChangedListener(
                 mActiveSessionsChangedListener, null, new Handler(looper));
-        mMediaSessionManager.setCallback(mButtonDispatchCallback, null);
+        mMediaSessionManager.addOnMediaKeyEventSessionChangedListener(
+                mContext.getMainExecutor(), mMediaKeyEventSessionChangedListener);
     }
 
     void init(AvrcpTargetService.ListCallback callback) {
@@ -200,7 +202,8 @@
         mActivePlayerId = NO_ACTIVE_PLAYER;
 
         mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveSessionsChangedListener);
-        mMediaSessionManager.setCallback(null, null);
+        mMediaSessionManager.removeOnMediaKeyEventSessionChangedListener(
+                mMediaKeyEventSessionChangedListener);
         mMediaSessionManager = null;
 
         mAudioManager.unregisterAudioPlaybackCallback(mAudioPlaybackCallback);
@@ -691,10 +694,12 @@
             boolean isActive = false;
             Log.v(TAG, "onPlaybackConfigChanged(): Configs list size=" + configs.size());
             for (AudioPlaybackConfiguration config : configs) {
-                if (config.isActive()) {
+                if (config.isActive() && (config.getAudioAttributes().getUsage()
+                            == AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+                        && (config.getAudioAttributes().getContentType()
+                            == AudioAttributes.CONTENT_TYPE_SPEECH)) {
                     if (DEBUG) {
-                        Log.d(TAG, "onPlaybackConfigChanged(): config="
-                                 + AudioPlaybackConfiguration.toLogFriendlyString(config));
+                        Log.d(TAG, "onPlaybackConfigChanged(): config=" + config);
                     }
                     isActive = true;
                 }
@@ -738,61 +743,45 @@
         }
     };
 
-    private final MediaSessionManager.Callback mButtonDispatchCallback =
-            new MediaSessionManager.Callback() {
+    private final MediaSessionManager.OnMediaKeyEventSessionChangedListener
+            mMediaKeyEventSessionChangedListener =
+            new MediaSessionManager.OnMediaKeyEventSessionChangedListener() {
                 @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);
-
+                public void onMediaKeyEventSessionChanged(String packageName,
+                        MediaSession.Token token) {
                     if (mMediaSessionManager == null) {
-                        Log.w(TAG, "onAddressedPlayerChanged(Token): Unexpected callback "
-                                + "from the MediaSessionManager");
+                        Log.w(TAG, "onMediaKeyEventSessionChanged(): Unexpected callback "
+                                + "from the MediaSessionManager, pkg" + packageName + ", token="
+                                + token);
                         return;
                     }
-
-                    if (!mMediaPlayerIds.containsKey(controller.getPackageName())) {
-                        // Since we have a controller, we can try to to recover by adding the
-                        // player and then setting it as active.
-                        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 (mMediaSessionManager == null) {
-                        Log.w(TAG, "onAddressedPlayerChanged(Component): Unexpected callback "
-                                + "from the MediaSessionManager");
+                    if (TextUtils.isEmpty(packageName)) {
                         return;
                     }
+                    if (token != null) {
+                        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, "onMediaKeyEventSessionChanged(Token): Addressed Player "
+                                    + "changed to a player we didn't have a session for");
+                            addMediaPlayer(controller);
+                        }
 
-                    if (receiver == null) {
-                        return;
+                        Log.i(TAG, "onMediaKeyEventSessionChanged: token="
+                                + controller.getPackageName());
+                        setActivePlayer(mMediaPlayerIds.get(controller.getPackageName()));
+                    } else {
+                        if (!mMediaPlayerIds.containsKey(packageName)) {
+                            e("onMediaKeyEventSessionChanged(PackageName): Media key event session "
+                                    + "changed to a player we don't have a session for");
+                            return;
+                        }
+
+                        Log.i(TAG, "onMediaKeyEventSessionChanged: packageName=" + packageName);
+                        setActivePlayer(mMediaPlayerIds.get(packageName));
                     }
-
-                    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()));
                 }
             };
 
@@ -821,7 +810,7 @@
 
     private static void e(String message) {
         if (sTesting) {
-            Log.wtfStack(TAG, message);
+            Log.wtf(TAG, message);
         } else {
             Log.e(TAG, message);
         }
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
index 230d876..38a270f 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
@@ -312,7 +312,7 @@
 
             // TODO(apanicke): Add metric collection here.
 
-            if (sTesting) Log.wtfStack(TAG, "Crashing the stack");
+            if (sTesting) Log.wtf(TAG, "Crashing the stack");
         }
     }
 
@@ -486,7 +486,7 @@
 
     private static void e(String message) {
         if (sTesting) {
-            Log.wtfStack(TAG, message);
+            Log.wtf(TAG, message);
         } else {
             Log.e(TAG, message);
         }
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java b/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
index d6e846d..7bf38bd 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
@@ -160,6 +160,6 @@
     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");
+        Log.wtf("AvrcpMockMediaBrowser", "This function should never be called");
     }
 }
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaController.java b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
index 5c7b73f..0d160bc 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaController.java
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
@@ -215,14 +215,14 @@
      * Wrapper for MediaController.controlsSameSession(MediaController other)
      */
     public boolean controlsSameSession(MediaController other) {
-        return mDelegate.controlsSameSession(other.getWrappedInstance());
+        return mDelegate.getSessionToken().equals(other.getWrappedInstance().getSessionToken());
     }
 
     /**
      * Wrapper for MediaController.controlsSameSession(MediaController other)
      */
     public boolean controlsSameSession(android.media.session.MediaController other) {
-        return mDelegate.controlsSameSession(other);
+        return mDelegate.getSessionToken().equals(other.getSessionToken());
     }
 
     /**
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
old mode 100644
new mode 100755
index 693b4a2..f46820c
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -172,9 +172,11 @@
             }
         }
 
+        // If we don't find a node in the tree then do not have any way to browse for the contents.
+        // Return an empty list instead.
         if (requestedNode == null) {
             if (DBG) Log.d(TAG, "Didn't find a node");
-            return null;
+            return new ArrayList(0);
         } else {
             if (!requestedNode.isCached()) {
                 if (DBG) Log.d(TAG, "node is not cached");
@@ -435,6 +437,18 @@
         }
     }
 
+    private void onAvailablePlayerChanged(byte[] address) {
+        if (DBG) {
+            Log.d(TAG," onAvailablePlayerChanged");
+        }
+        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+        AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED);
+        }
+    }
+
     // Browsing related JNI callbacks.
     void handleGetFolderItemsRsp(byte[] address, int status, MediaItem[] items) {
         if (DBG) {
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
old mode 100644
new mode 100755
index e3abc4d..95867a9
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -37,8 +37,8 @@
 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 com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -79,6 +79,7 @@
     static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
     static final int MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS = 217;
     static final int MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS = 218;
+    static final int MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED = 219;
 
     //300->399 Events for Browsing
     static final int MESSAGE_GET_FOLDER_ITEMS = 300;
@@ -296,9 +297,9 @@
         @Override
         public void enter() {
             if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
+                BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
                 BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
                 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
-                BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
             } else {
                 logD("ReEnteringConnected");
             }
@@ -400,6 +401,10 @@
                     BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
                     return true;
 
+                case MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED:
+                    processAvailablePlayerChanged();
+                    return true;
+
                 case DISCONNECT:
                     transitionTo(mDisconnecting);
                     return true;
@@ -471,6 +476,13 @@
                             PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
                                     PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)});
         }
+
+        private void processAvailablePlayerChanged() {
+            logD("processAvailablePlayerChanged");
+            mBrowseTree.mRootNode.setCached(false);
+            mBrowseTree.mRootNode.setExpectedChildren(255);
+            BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
+        }
     }
 
     // Handle the get folder listing action
@@ -881,7 +893,6 @@
 
     private boolean shouldRequestFocus() {
         return mService.getResources()
-                .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus)
-                || !mAudioManager.isMusicActive();
+                .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
     }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index a0b1224..df88b80 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -63,6 +63,16 @@
     // Browsing related structures.
     private List<MediaSessionCompat.QueueItem> mMediaQueue = new ArrayList<>();
 
+    // Media Framework Content Style constants
+    private static final String CONTENT_STYLE_SUPPORTED =
+            "android.media.browse.CONTENT_STYLE_SUPPORTED";
+    public static final String CONTENT_STYLE_PLAYABLE_HINT =
+            "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT";
+    public static final String CONTENT_STYLE_BROWSABLE_HINT =
+            "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT";
+    public static final int CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1;
+    public static final int CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2;
+
     // Error messaging extras
     public static final String ERROR_RESOLUTION_ACTION_INTENT =
             "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
@@ -117,6 +127,14 @@
         mSession.setPlaybackState(errorState);
     }
 
+    private Bundle getDefaultStyle() {
+        Bundle style = new Bundle();
+        style.putBoolean(CONTENT_STYLE_SUPPORTED, true);
+        style.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE);
+        style.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE);
+        return style;
+    }
+
     @Override
     public synchronized void onLoadChildren(final String parentMediaId,
             final Result<List<MediaBrowserCompat.MediaItem>> result) {
@@ -133,7 +151,8 @@
     @Override
     public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
         if (DBG) Log.d(TAG, "onGetRoot");
-        return new BrowserRoot(BrowseTree.ROOT, null);
+        Bundle style = getDefaultStyle();
+        return new BrowserRoot(BrowseTree.ROOT, style);
     }
 
     private void updateNowPlayingQueue(BrowseTree.BrowseNode node) {
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index 4b4b4df..8418e0e 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -43,10 +43,10 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
-import android.util.StatsLog;
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
 
@@ -580,8 +580,8 @@
         Log.d(TAG,
                 "PROFILE_CONNECTION_STATE_CHANGE: profile=" + profile + ", device=" + device + ", "
                         + prevState + " -> " + state);
-        StatsLog.write(StatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state, 0 /* deprecated */,
-                profile, mService.obfuscateAddress(device));
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state,
+                0 /* deprecated */, profile, mService.obfuscateAddress(device));
 
         if (!isNormalStateTransition(prevState, state)) {
             Log.w(TAG,
@@ -916,15 +916,6 @@
         setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
     }
 
-    void onBluetoothDisable() {
-        // From STATE_ON to BLE_ON
-        debugLog("onBluetoothDisable()");
-        // 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) {
         infoLog("Callback:discoveryStateChangeCallback with state:" + state);
         synchronized (mObject) {
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index e4a5ae0..09f2166 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -16,6 +16,16 @@
 
 package com.android.bluetooth.btservice;
 
+import static com.android.bluetooth.Utils.addressToBytes;
+import static com.android.bluetooth.Utils.callerIsSystemOrActiveOrManagedUser;
+import static com.android.bluetooth.Utils.callerIsSystemOrActiveUser;
+import static com.android.bluetooth.Utils.enforceBluetoothAdminPermission;
+import static com.android.bluetooth.Utils.enforceBluetoothPermission;
+import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
+import static com.android.bluetooth.Utils.enforceDumpPermission;
+import static com.android.bluetooth.Utils.enforceLocalMacAddressPermission;
+
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
@@ -23,6 +33,7 @@
 import android.app.Service;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAdapter.ActiveDeviceUse;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -50,7 +61,6 @@
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.PowerManager;
-import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -63,11 +73,10 @@
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
-import android.util.Slog;
 import android.util.SparseArray;
-import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.a2dpsink.A2dpSinkService;
@@ -90,6 +99,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.ArrayUtils;
 
 import com.google.protobuf.InvalidProtocolBufferException;
 
@@ -100,6 +110,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 
 public class AdapterService extends Service {
     private static final String TAG = "BluetoothAdapterService";
@@ -205,7 +216,6 @@
     private String mWakeLockName;
     private UserManager mUserManager;
 
-    private ProfileObserver mProfileObserver;
     private PhonePolicy mPhonePolicy;
     private ActiveDeviceManager mActiveDeviceManager;
     private DatabaseManager mDatabaseManager;
@@ -440,8 +450,6 @@
 
         mSdpManager = SdpManager.init(this);
         registerReceiver(mAlarmBroadcastReceiver, new IntentFilter(ACTION_ALARM_WAKEUP));
-        mProfileObserver = new ProfileObserver(getApplicationContext(), this, new Handler());
-        mProfileObserver.start();
 
         // Phone policy is specific to phone implementations and hence if a device wants to exclude
         // it out then it can be disabled by using the flag below.
@@ -483,9 +491,9 @@
         }.execute();
 
         try {
-            int systemUiUid = getApplicationContext().getPackageManager().getPackageUidAsUser(
-                    "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
-                    UserHandle.USER_SYSTEM);
+            int systemUiUid = getApplicationContext().getPackageManager().getPackageUid(
+                    "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY);
+
             Utils.setSystemUiUid(systemUiUid);
         } catch (PackageManager.NameNotFoundException e) {
             // Some platforms, such as wearables do not have a system ui.
@@ -493,8 +501,7 @@
         }
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-        getApplicationContext().registerReceiverAsUser(sUserSwitchedReceiver, UserHandle.ALL,
-                filter, null, null);
+        getApplicationContext().registerReceiverForAllUsers(sUserSwitchedReceiver, filter, null, null);
         int fuid = ActivityManager.getCurrentUser();
         Utils.setForegroundUserId(fuid);
     }
@@ -515,7 +522,6 @@
     @Override
     public void onDestroy() {
         debugLog("onDestroy()");
-        mProfileObserver.stop();
         if (!isMock()) {
             // TODO(b/27859763)
             Log.i(TAG, "Force exit to cleanup internal state in Bluetooth stack");
@@ -563,8 +569,8 @@
         } catch (RemoteException e) {
             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);
+        BluetoothStatsLog.write_non_chained(BluetoothStatsLog.BLE_SCAN_STATE_CHANGED, -1, null,
+                BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__RESET, false, false, false);
 
         //Start Gatt service
         setProfileServiceState(GattService.class, BluetoothAdapter.STATE_ON);
@@ -600,17 +606,6 @@
                 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();
@@ -626,7 +621,10 @@
     }
 
     void stopProfileServices() {
-        mAdapterProperties.onBluetoothDisable();
+        // Make sure to stop classic background tasks now
+        cancelDiscoveryNative();
+        mAdapterProperties.setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
+
         Class[] supportedProfileServices = Config.getSupportedProfiles();
         if (supportedProfileServices.length == 1 && (mRunningProfiles.size() == 1
                 && GattService.class.getSimpleName().equals(mRunningProfiles.get(0).getName()))) {
@@ -815,36 +813,36 @@
         }
 
         if (profile == BluetoothProfile.HEADSET) {
-            return (BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.HSP_AG)
-                    && BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.HSP))
-                    || (BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.Handsfree_AG)
-                    && BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Handsfree));
+            return (ArrayUtils.contains(localDeviceUuids, BluetoothUuid.HSP_AG)
+                    && ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HSP))
+                    || (ArrayUtils.contains(localDeviceUuids, BluetoothUuid.HFP_AG)
+                    && ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HFP));
         }
         if (profile == BluetoothProfile.HEADSET_CLIENT) {
-            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Handsfree_AG)
-                    && BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.Handsfree);
+            return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HFP_AG)
+                    && ArrayUtils.contains(localDeviceUuids, BluetoothUuid.HFP);
         }
         if (profile == BluetoothProfile.A2DP) {
-            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AdvAudioDist)
-                    || BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AudioSink);
+            return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.ADV_AUDIO_DIST)
+                    || ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.A2DP_SINK);
         }
         if (profile == BluetoothProfile.A2DP_SINK) {
-            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AdvAudioDist)
-                    || BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AudioSource);
+            return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.ADV_AUDIO_DIST)
+                    || ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.A2DP_SOURCE);
         }
         if (profile == BluetoothProfile.OPP) {
-            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.ObexObjectPush);
+            return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.OBEX_OBJECT_PUSH);
         }
         if (profile == BluetoothProfile.HID_HOST) {
-            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Hid)
-                    || BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Hogp);
+            return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HID)
+                    || ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HOGP);
         }
         if (profile == BluetoothProfile.HID_DEVICE) {
             return mHidDeviceService.getConnectionState(device)
                     == BluetoothProfile.STATE_DISCONNECTED;
         }
         if (profile == BluetoothProfile.PAN) {
-            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.NAP);
+            return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.NAP);
         }
         if (profile == BluetoothProfile.MAP) {
             return mMapService.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED;
@@ -856,14 +854,14 @@
             return true;
         }
         if (profile == BluetoothProfile.PBAP_CLIENT) {
-            return BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.PBAP_PCE)
-                    && BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.PBAP_PSE);
+            return ArrayUtils.contains(localDeviceUuids, BluetoothUuid.PBAP_PCE)
+                    && ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.PBAP_PSE);
         }
         if (profile == BluetoothProfile.HEARING_AID) {
-            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.HearingAid);
+            return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HEARING_AID);
         }
         if (profile == BluetoothProfile.SAP) {
-            return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.SAP);
+            return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.SAP);
         }
 
         Log.e(TAG, "isSupported: Unexpected profile passed in to function: " + profile);
@@ -878,40 +876,40 @@
      */
     private boolean isAnyProfileEnabled(BluetoothDevice device) {
 
-        if (mA2dpService != null
-                && mA2dpService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+        if (mA2dpService != null && mA2dpService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return true;
         }
-        if (mA2dpSinkService != null
-                && mA2dpSinkService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+        if (mA2dpSinkService != null && mA2dpSinkService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return true;
         }
-        if (mHeadsetService != null
-                && mHeadsetService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+        if (mHeadsetService != null && mHeadsetService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return true;
         }
-        if (mHeadsetClientService != null
-                && mHeadsetClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+        if (mHeadsetClientService != null && mHeadsetClientService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return true;
         }
-        if (mMapClientService != null
-                && mMapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+        if (mMapClientService != null && mMapClientService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return true;
         }
-        if (mHidHostService != null
-                && mHidHostService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+        if (mHidHostService != null && mHidHostService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return true;
         }
-        if (mPanService != null
-                && mPanService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+        if (mPanService != null && mPanService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return true;
         }
-        if (mPbapClientService != null
-                && mPbapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+        if (mPbapClientService != null && mPbapClientService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return true;
         }
-        if (mHearingAidService != null
-                && mHearingAidService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+        if (mHearingAidService != null && mHearingAidService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return true;
         }
 
@@ -919,66 +917,71 @@
     }
 
     /**
-     * Connects only available profiles (those with {@link BluetoothProfile#PRIORITY_ON})
+     * Connects only available profiles
+     * (those with {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED})
      *
      * @param device is the device with which we are connecting the profiles
      * @return true
      */
     private boolean connectEnabledProfiles(BluetoothDevice device) {
         ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
-        ParcelUuid[] localDeviceUuids = getUuids();
+        ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
 
         if (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
-                BluetoothProfile.A2DP, device)
-                && mA2dpService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+                BluetoothProfile.A2DP, device) && mA2dpService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.i(TAG, "connectEnabledProfiles: Connecting A2dp");
             mA2dpService.connect(device);
         }
         if (mA2dpSinkService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
-                BluetoothProfile.A2DP_SINK, device)
-                && mA2dpSinkService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+                BluetoothProfile.A2DP_SINK, device) && mA2dpSinkService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.i(TAG, "connectEnabledProfiles: Connecting A2dp Sink");
             mA2dpSinkService.connect(device);
         }
         if (mHeadsetService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
-                BluetoothProfile.HEADSET, device)
-                && mHeadsetService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+                BluetoothProfile.HEADSET, device) && mHeadsetService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.i(TAG, "connectEnabledProfiles: Connecting Headset Profile");
             mHeadsetService.connect(device);
         }
         if (mHeadsetClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HEADSET_CLIENT, device)
-                && mHeadsetClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+                && mHeadsetClientService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.i(TAG, "connectEnabledProfiles: Connecting HFP");
             mHeadsetClientService.connect(device);
         }
         if (mMapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.MAP_CLIENT, device)
-                && mMapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+                && mMapClientService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.i(TAG, "connectEnabledProfiles: Connecting MAP");
             mMapClientService.connect(device);
         }
         if (mHidHostService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
-                BluetoothProfile.HID_HOST, device)
-                && mHidHostService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+                BluetoothProfile.HID_HOST, device) && mHidHostService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.i(TAG, "connectEnabledProfiles: Connecting Hid Host Profile");
             mHidHostService.connect(device);
         }
         if (mPanService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
-                BluetoothProfile.PAN, device)
-                && mPanService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+                BluetoothProfile.PAN, device) && mPanService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.i(TAG, "connectEnabledProfiles: Connecting Pan Profile");
             mPanService.connect(device);
         }
         if (mPbapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.PBAP_CLIENT, device)
-                && mPbapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+                && mPbapClientService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.i(TAG, "connectEnabledProfiles: Connecting Pbap");
             mPbapClientService.connect(device);
         }
         if (mHearingAidService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HEARING_AID, device)
-                && mHearingAidService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+                && mHearingAidService.getConnectionPolicy(device)
+                > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.i(TAG, "connectEnabledProfiles: Connecting Hearing Aid Profile");
             mHearingAidService.connect(device);
         }
@@ -1041,7 +1044,8 @@
      * why an inner instance class should be avoided.
      *
      */
-    private static class AdapterServiceBinder extends IBluetooth.Stub {
+    @VisibleForTesting
+    public static class AdapterServiceBinder extends IBluetooth.Stub {
         private AdapterService mService;
 
         AdapterServiceBinder(AdapterService svc) {
@@ -1060,307 +1064,287 @@
         }
 
         @Override
-        public boolean isEnabled() {
-            // don't check caller, may be called from system UI
-            AdapterService service = getService();
-            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;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.getState();
         }
 
         @Override
-        public boolean enable() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
-                Log.w(TAG, "enable() - Not allowed for non-active user and non system user");
-                return false;
-            }
+        public boolean enable(boolean quietMode) {
             AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.enable();
-        }
-
-        @Override
-        public boolean enableNoAutoConnect() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
-                Log.w(TAG, "enableNoAuto() - Not allowed for non-active user and non system user");
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "enable")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.enableNoAutoConnect();
+            enforceBluetoothAdminPermission(service);
+
+            return service.enable(quietMode);
         }
 
         @Override
         public boolean disable() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
-                Log.w(TAG, "disable() - Not allowed for non-active user and non system user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "disable")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
+            enforceBluetoothAdminPermission(service);
+
             return service.disable();
         }
 
         @Override
         public String getAddress() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID)
-                    && (!Utils.checkCallerAllowManagedProfiles(mService))) {
-                Log.w(TAG, "getAddress() - Not allowed for non-active user and non system user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAddress")) {
                 return null;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return null;
-            }
-            return service.getAddress();
+            enforceBluetoothPermission(service);
+            enforceLocalMacAddressPermission(service);
+
+            return Utils.getAddressStringFromByte(service.mAdapterProperties.getAddress());
         }
 
         @Override
         public ParcelUuid[] getUuids() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "getUuids() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getUuids")) {
                 return new ParcelUuid[0];
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return new ParcelUuid[0];
-            }
-            return service.getUuids();
+            enforceBluetoothPermission(service);
+
+            return service.mAdapterProperties.getUuids();
         }
 
         @Override
         public String getName() {
-            if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
-                Log.w(TAG, "getName() - Not allowed for non-active user and non system user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getName")) {
                 return null;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return null;
-            }
+            enforceBluetoothPermission(service);
+
             return service.getName();
         }
 
         @Override
         public boolean setName(String name) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setName() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setName")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.setName(name);
+            enforceBluetoothAdminPermission(service);
+
+            return service.mAdapterProperties.setName(name);
         }
 
         @Override
         public BluetoothClass getBluetoothClass() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "getBluetoothClass() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getBluetoothClass")) {
                 return null;
             }
 
-            AdapterService service = getService();
-            if (service == null) return null;
-            return service.getBluetoothClass();
+            enforceBluetoothAdminPermission(service);
+
+            return service.mAdapterProperties.getBluetoothClass();
         }
 
         @Override
         public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setBluetoothClass")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
+            enforceBluetoothPrivilegedPermission(service);
+
+            if (!service.mAdapterProperties.setBluetoothClass(bluetoothClass)) {
+              return false;
             }
-            return service.setBluetoothClass(bluetoothClass);
+
+            return Settings.Global.putInt(
+                    service.getContentResolver(),
+                    Settings.Global.BLUETOOTH_CLASS_OF_DEVICE,
+                    bluetoothClass.getClassOfDevice());
         }
 
         @Override
         public int getIoCapability() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getIoCapability")) {
                 return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
             }
 
-            AdapterService service = getService();
-            if (service == null) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
-            return service.getIoCapability();
+            enforceBluetoothAdminPermission(service);
+
+            return service.mAdapterProperties.getIoCapability();
         }
 
         @Override
         public boolean setIoCapability(int capability) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setIoCapability")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) return false;
-            return service.setIoCapability(capability);
+            enforceBluetoothPrivilegedPermission(service);
+
+            if (!isValidIoCapability(capability)) {
+              return false;
+            }
+
+            return service.mAdapterProperties.setIoCapability(capability);
         }
 
         @Override
         public int getLeIoCapability() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getLeIoCapability")) {
                 return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
             }
 
-            AdapterService service = getService();
-            if (service == null) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
-            return service.getLeIoCapability();
+            enforceBluetoothAdminPermission(service);
+
+            return service.mAdapterProperties.getLeIoCapability();
         }
 
         @Override
         public boolean setLeIoCapability(int capability) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setLeIoCapability")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) return false;
-            return service.setLeIoCapability(capability);
+            enforceBluetoothPrivilegedPermission(service);
+
+            if (!isValidIoCapability(capability)) {
+              return false;
+            }
+
+            return service.mAdapterProperties.setLeIoCapability(capability);
         }
 
         @Override
         public int getScanMode() {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "getScanMode() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getScanMode")) {
                 return BluetoothAdapter.SCAN_MODE_NONE;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return BluetoothAdapter.SCAN_MODE_NONE;
-            }
-            return service.getScanMode();
+            enforceBluetoothPermission(service);
+
+            return service.mAdapterProperties.getScanMode();
         }
 
         @Override
         public boolean setScanMode(int mode, int duration) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setScanMode() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setScanMode")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.setScanMode(mode, duration);
+            enforceBluetoothPrivilegedPermission(service);
+
+            service.mAdapterProperties.setDiscoverableTimeout(duration);
+            return service.mAdapterProperties.setScanMode(convertScanModeToHal(mode));
         }
 
         @Override
         public int getDiscoverableTimeout() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "getDiscoverableTimeout() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getDiscoverableTimeout")) {
                 return 0;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return 0;
-            }
-            return service.getDiscoverableTimeout();
+            enforceBluetoothPermission(service);
+
+            return service.mAdapterProperties.getDiscoverableTimeout();
         }
 
         @Override
         public boolean setDiscoverableTimeout(int timeout) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setDiscoverableTimeout() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setDiscoverableTimeout")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.setDiscoverableTimeout(timeout);
+            enforceBluetoothPermission(service);
+
+            return service.mAdapterProperties.setDiscoverableTimeout(timeout);
         }
 
         @Override
-        public boolean startDiscovery(String callingPackage) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "startDiscovery() - Not allowed for non-active user");
+        public boolean startDiscovery(String callingPackage, String callingFeatureId) {
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "startDiscovery")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.startDiscovery(callingPackage);
+            enforceBluetoothAdminPermission(service);
+
+            return service.startDiscovery(callingPackage, callingFeatureId);
         }
 
         @Override
         public boolean cancelDiscovery() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "cancelDiscovery() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelDiscovery")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.cancelDiscovery();
+            enforceBluetoothAdminPermission(service);
+
+            service.debugLog("cancelDiscovery");
+            return service.cancelDiscoveryNative();
         }
 
         @Override
         public boolean isDiscovering() {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "isDiscovering() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "isDiscovering")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.isDiscovering();
+            enforceBluetoothPermission(service);
+
+            return service.mAdapterProperties.isDiscovering();
         }
 
         @Override
         public long getDiscoveryEndMillis() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "getDiscoveryEndMillis() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getDiscoveryEndMillis")) {
                 return -1;
             }
 
+            enforceBluetoothPermission(service);
+
+            return service.mAdapterProperties.discoveryEndMillis();
+        }
+
+        @Override
+        public List<BluetoothDevice> getMostRecentlyConnectedDevices() {
+            // don't check caller, may be called from system UI
             AdapterService service = getService();
             if (service == null) {
-                return -1;
+                return new ArrayList<>();
             }
-            return service.getDiscoveryEndMillis();
+
+            enforceBluetoothAdminPermission(service);
+
+            return service.mDatabaseManager.getMostRecentlyConnectedDevices();
         }
 
         @Override
@@ -1370,6 +1354,9 @@
             if (service == null) {
                 return new BluetoothDevice[0];
             }
+
+            enforceBluetoothPermission(service);
+
             return service.getBondedDevices();
         }
 
@@ -1380,77 +1367,72 @@
             if (service == null) {
                 return BluetoothAdapter.STATE_DISCONNECTED;
             }
-            return service.getAdapterConnectionState();
+
+            enforceBluetoothPermission(service);
+
+            return service.mAdapterProperties.getConnectionState();
         }
 
         @Override
         public int getProfileConnectionState(int profile) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "getProfileConnectionState- Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getProfileConnectionState")) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-            return service.getProfileConnectionState(profile);
+            enforceBluetoothPermission(service);
+
+            return service.mAdapterProperties.getProfileConnectionState(profile);
         }
 
         @Override
-        public boolean createBond(BluetoothDevice device, int transport) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "createBond() - Not allowed for non-active user");
-                return false;
-            }
-
+        public boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
             AdapterService service = getService();
-            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");
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "createBond")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
+            enforceBluetoothAdminPermission(service);
+
             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");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelBondProcess")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
+            enforceBluetoothAdminPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            if (deviceProp != null) {
+                deviceProp.setBondingInitiatedLocally(false);
             }
-            return service.cancelBondProcess(device);
+
+            return service.cancelBondNative(addressToBytes(device.getAddress()));
         }
 
         @Override
         public boolean removeBond(BluetoothDevice device) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "removeBond() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "removeBond")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
+            enforceBluetoothAdminPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDED) {
                 return false;
             }
-            return service.removeBond(device);
+            deviceProp.setBondingInitiatedLocally(false);
+
+            Message msg = service.mBondStateMachine.obtainMessage(BondStateMachine.REMOVE_BOND);
+            msg.obj = device;
+            service.mBondStateMachine.sendMessage(msg);
+            return true;
         }
 
         @Override
@@ -1460,6 +1442,9 @@
             if (service == null) {
                 return BluetoothDevice.BOND_NONE;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.getBondState(device);
         }
 
@@ -1470,7 +1455,11 @@
             if (service == null) {
                 return false;
             }
-            return service.isBondingInitiatedLocally(device);
+
+            enforceBluetoothPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            return deviceProp != null && deviceProp.isBondingInitiatedLocally();
         }
 
         @Override
@@ -1479,7 +1468,7 @@
             if (service == null) {
                 return 0;
             }
-            return service.getSupportedProfiles();
+            return Config.getSupportedProfilesBitMask();
         }
 
         @Override
@@ -1488,13 +1477,16 @@
             if (service == null) {
                 return 0;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.getConnectionState(device);
         }
 
         @Override
-        public boolean connectAllEnabledProfiles(BluetoothDevice device) {
+        public boolean removeActiveDevice(@ActiveDeviceUse int profiles) {
             if (!Utils.checkCaller()) {
-                Log.w(TAG, "connectAllEnabledProfiles() - Not allowed for non-active user");
+                Log.w(TAG, "removeActiveDevice() - Not allowed for non-active user");
                 return false;
             }
 
@@ -1502,284 +1494,308 @@
             if (service == null) {
                 return false;
             }
+            return service.setActiveDevice(null, profiles);
+        }
+
+        @Override
+        public boolean setActiveDevice(BluetoothDevice device, @ActiveDeviceUse int profiles) {
+            if (!Utils.checkCaller()) {
+                Log.w(TAG, "setActiveDevice() - Not allowed for non-active user");
+                return false;
+            }
+
+            AdapterService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setActiveDevice(device, profiles);
+        }
+
+        @Override
+        public boolean connectAllEnabledProfiles(BluetoothDevice device) {
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "connectAllEnabledProfiles")) {
+                return false;
+            }
+
+            enforceBluetoothPrivilegedPermission(service);
+
             return service.connectAllEnabledProfiles(device);
         }
 
         @Override
         public boolean disconnectAllEnabledProfiles(BluetoothDevice device) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "disconnectAllEnabledProfiles() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null | !callerIsSystemOrActiveUser(TAG, "disconnectAllEnabledProfiles")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
+            enforceBluetoothPrivilegedPermission(service);
+
             return service.disconnectAllEnabledProfiles(device);
         }
 
         @Override
         public String getRemoteName(BluetoothDevice device) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "getRemoteName() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteName")) {
                 return null;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return null;
-            }
+            enforceBluetoothPermission(service);
+
             return service.getRemoteName(device);
         }
 
         @Override
         public int getRemoteType(BluetoothDevice device) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "getRemoteType() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteType")) {
                 return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
-            }
-            return service.getRemoteType(device);
+            enforceBluetoothPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            return deviceProp != null ? deviceProp.getDeviceType() : BluetoothDevice.DEVICE_TYPE_UNKNOWN;
         }
 
         @Override
         public String getRemoteAlias(BluetoothDevice device) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "getRemoteAlias() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteAlias")) {
                 return null;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return null;
-            }
-            return service.getRemoteAlias(device);
+            enforceBluetoothPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            return deviceProp != null ? deviceProp.getAlias() : null;
         }
 
         @Override
         public boolean setRemoteAlias(BluetoothDevice device, String name) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setRemoteAlias() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setRemoteAlias")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
+            enforceBluetoothPrivilegedPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            if (deviceProp == null) {
                 return false;
             }
-            return service.setRemoteAlias(device, name);
+            deviceProp.setAlias(device, name);
+            return true;
         }
 
         @Override
         public int getRemoteClass(BluetoothDevice device) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "getRemoteClass() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteClass")) {
                 return 0;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return 0;
-            }
-            return service.getRemoteClass(device);
+            enforceBluetoothPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            return deviceProp != null ? deviceProp.getBluetoothClass() : 0;
         }
 
         @Override
         public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "getRemoteUuids() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteUuids")) {
                 return new ParcelUuid[0];
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return new ParcelUuid[0];
-            }
+            enforceBluetoothPermission(service);
+
             return service.getRemoteUuids(device);
         }
 
         @Override
         public boolean fetchRemoteUuids(BluetoothDevice device) {
-            if (!Utils.checkCallerAllowManagedProfiles(mService)) {
-                Log.w(TAG, "fetchRemoteUuids() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "fetchRemoteUuids")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.fetchRemoteUuids(device);
+            enforceBluetoothPermission(service);
+
+            service.mRemoteDevices.fetchUuids(device);
+            return true;
         }
 
 
         @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");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPin")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
+            enforceBluetoothAdminPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            // Only allow setting a pin in bonding state, or bonded state in case of security upgrade.
+            if (deviceProp == null || !deviceProp.isBondingOrBonded()) {
                 return false;
             }
-            return service.setPin(device, accept, len, pinCode);
+            if (pinCode.length != len) {
+                android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+                        "PIN code length mismatch");
+                return false;
+            }
+            service.logUserBondResponse(device, accept, BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REPLIED);
+            return service.pinReplyNative(addressToBytes(device.getAddress()), 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");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPasskey")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
+            enforceBluetoothPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            if (deviceProp == null || !deviceProp.isBonding()) {
                 return false;
             }
-            return service.setPasskey(device, accept, len, passkey);
+            if (passkey.length != len) {
+                android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+                        "Passkey length mismatch");
+                return false;
+            }
+            service.logUserBondResponse(device, accept, BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED);
+            return service.sspReplyNative(
+                    addressToBytes(device.getAddress()),
+                    AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY,
+                    accept,
+                    Utils.byteArrayToInt(passkey));
         }
 
         @Override
         public boolean setPairingConfirmation(BluetoothDevice device, boolean accept) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setPairingConfirmation() - Not allowed for non-active user");
-                return false;
-            }
-
             AdapterService service = getService();
-            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");
-                return BluetoothDevice.ACCESS_UNKNOWN;
-            }
-
-            AdapterService service = getService();
-            if (service == null) {
-                return BluetoothDevice.ACCESS_UNKNOWN;
-            }
-            return service.getPhonebookAccessPermission(device);
-        }
-
-        @Override
-        public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setSilenceMode() - Not allowed for non-active user");
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPairingConfirmation")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
+            enforceBluetoothPrivilegedPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            if (deviceProp == null || !deviceProp.isBonding()) {
                 return false;
             }
-            return service.setSilenceMode(device, silence);
+            service.logUserBondResponse(device, accept, BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED);
+            return service.sspReplyNative(
+                    addressToBytes(device.getAddress()),
+                    AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION,
+                    accept,
+                    0);
         }
 
         @Override
         public boolean getSilenceMode(BluetoothDevice device) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "getSilenceMode() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getSilenceMode")) {
                 return false;
             }
 
+            enforceBluetoothPrivilegedPermission(service);
+
+            return service.mSilenceDeviceManager.getSilenceMode(device);
+        }
+
+
+        @Override
+        public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setSilenceMode")) {
                 return false;
             }
-            return service.getSilenceMode(device);
+
+            enforceBluetoothPrivilegedPermission(service);
+
+            service.mSilenceDeviceManager.setSilenceMode(device, silence);
+            return true;
+        }
+
+        @Override
+        public int getPhonebookAccessPermission(BluetoothDevice device) {
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getPhonebookAccessPermission")) {
+                return BluetoothDevice.ACCESS_UNKNOWN;
+            }
+
+            enforceBluetoothPermission(service);
+
+            return service.getDeviceAccessFromPrefs(device, PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE);
         }
 
         @Override
         public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setPhonebookAccessPermission() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPhonebookAccessPermission")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.setPhonebookAccessPermission(device, value);
+            service.setPhonebookAccessPermission(device, value);
+            return true;
         }
 
         @Override
         public int getMessageAccessPermission(BluetoothDevice device) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "getMessageAccessPermission() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getMessageAccessPermission")) {
                 return BluetoothDevice.ACCESS_UNKNOWN;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return BluetoothDevice.ACCESS_UNKNOWN;
-            }
-            return service.getMessageAccessPermission(device);
+            enforceBluetoothPermission(service);
+
+            return service.getDeviceAccessFromPrefs(device, MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE);
         }
 
         @Override
         public boolean setMessageAccessPermission(BluetoothDevice device, int value) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setMessageAccessPermission() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setMessageAccessPermission")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.setMessageAccessPermission(device, value);
+            enforceBluetoothPrivilegedPermission(service);
+
+            service.setMessageAccessPermission(device, value);
+            return true;
         }
 
         @Override
         public int getSimAccessPermission(BluetoothDevice device) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "getSimAccessPermission() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getSimAccessPermission")) {
                 return BluetoothDevice.ACCESS_UNKNOWN;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return BluetoothDevice.ACCESS_UNKNOWN;
-            }
-            return service.getSimAccessPermission(device);
+            enforceBluetoothPermission(service);
+
+            return service.getDeviceAccessFromPrefs(device, SIM_ACCESS_PERMISSION_PREFERENCE_FILE);
         }
 
         @Override
         public boolean setSimAccessPermission(BluetoothDevice device, int value) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "setSimAccessPermission() - Not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setSimAccessPermission")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.setSimAccessPermission(device, value);
-        }
+            enforceBluetoothPrivilegedPermission(service);
 
-        @Override
-        public void sendConnectionStateChange(BluetoothDevice device, int profile, int state,
-                int prevState) {
-            AdapterService service = getService();
-            if (service == null) {
-                return;
-            }
-            service.sendConnectionStateChange(device, profile, state, prevState);
+            service.setSimAccessPermission(device, value);
+            return true;
         }
 
         @Override
@@ -1788,35 +1804,40 @@
             if (service == null) {
                 return null;
             }
-            return service.getSocketManager();
+
+            return IBluetoothSocketManager.Stub.asInterface(service.mBluetoothSocketManagerBinder);
         }
 
         @Override
         public boolean sdpSearch(BluetoothDevice device, ParcelUuid uuid) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "sdpSea(): not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "sdpSearch")) {
                 return false;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
+            enforceBluetoothPermission(service);
+
+            if (service.mSdpManager == null) {
                 return false;
             }
-            return service.sdpSearch(device, uuid);
+            service.mSdpManager.sdpSearch(device, uuid);
+            return true;
         }
 
         @Override
         public int getBatteryLevel(BluetoothDevice device) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "getBatteryLevel(): not allowed for non-active user");
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getBatteryLevel")) {
                 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
             }
 
-            AdapterService service = getService();
-            if (service == null) {
+            enforceBluetoothPermission(service);
+
+            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+            if (deviceProp == null) {
                 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
             }
-            return service.getBatteryLevel(device);
+            return deviceProp.getBatteryLevel();
         }
 
         @Override
@@ -1826,6 +1847,9 @@
             if (service == null) {
                 return AdapterProperties.MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.getMaxConnectedAudioDevices();
         }
 
@@ -1836,6 +1860,9 @@
             if (service == null) {
                 return false;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.isA2dpOffloadEnabled();
         }
 
@@ -1845,27 +1872,34 @@
             if (service == null) {
                 return false;
             }
+
+            enforceBluetoothPrivilegedPermission(service);
+
             service.disable();
-            return service.factoryReset();
-
+            if (service.mDatabaseManager != null) {
+                service.mDatabaseManager.factoryReset();
+            }
+            return service.factoryResetNative();
         }
 
         @Override
-        public void registerCallback(IBluetoothCallback cb) {
+        public void registerCallback(IBluetoothCallback callback) {
             AdapterService service = getService();
             if (service == null) {
                 return;
             }
-            service.registerCallback(cb);
+
+            service.mCallbacks.register(callback);
         }
 
         @Override
-        public void unregisterCallback(IBluetoothCallback cb) {
+        public void unregisterCallback(IBluetoothCallback callback) {
             AdapterService service = getService();
             if (service == null) {
                 return;
             }
-            service.unregisterCallback(cb);
+
+            service.mCallbacks.unregister(callback);
         }
 
         @Override
@@ -1874,7 +1908,11 @@
             if (service == null) {
                 return false;
             }
-            return service.isMultiAdvertisementSupported();
+
+            enforceBluetoothPermission(service);
+
+            int val = service.mAdapterProperties.getNumOfAdvertisementInstancesSupported();
+            return val >= MIN_ADVT_INSTANCES_FOR_MA;
         }
 
         @Override
@@ -1883,8 +1921,11 @@
             if (service == null) {
                 return false;
             }
+
+            enforceBluetoothPermission(service);
+
             int val = service.getNumOfOffloadedScanFilterSupported();
-            return (val >= MIN_OFFLOADED_FILTERS);
+            return val >= MIN_OFFLOADED_FILTERS;
         }
 
         @Override
@@ -1893,8 +1934,11 @@
             if (service == null) {
                 return false;
             }
+
+            enforceBluetoothPermission(service);
+
             int val = service.getOffloadedScanResultStorage();
-            return (val >= MIN_OFFLOADED_SCAN_STORAGE_BYTES);
+            return val >= MIN_OFFLOADED_SCAN_STORAGE_BYTES;
         }
 
         @Override
@@ -1903,6 +1947,9 @@
             if (service == null) {
                 return false;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.isLe2MPhySupported();
         }
 
@@ -1912,6 +1959,9 @@
             if (service == null) {
                 return false;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.isLeCodedPhySupported();
         }
 
@@ -1921,6 +1971,9 @@
             if (service == null) {
                 return false;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.isLeExtendedAdvertisingSupported();
         }
 
@@ -1930,6 +1983,9 @@
             if (service == null) {
                 return false;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.isLePeriodicAdvertisingSupported();
         }
 
@@ -1939,6 +1995,9 @@
             if (service == null) {
                 return 0;
             }
+
+            enforceBluetoothPermission(service);
+
             return service.getLeMaximumAdvertisingDataLength();
         }
 
@@ -1948,7 +2007,10 @@
             if (service == null) {
                 return false;
             }
-            return service.isActivityAndEnergyReportingSupported();
+
+            enforceBluetoothPrivilegedPermission(service);
+
+            return service.mAdapterProperties.isActivityAndEnergyReportingSupported();
         }
 
         @Override
@@ -1957,6 +2019,9 @@
             if (service == null) {
                 return null;
             }
+
+            enforceBluetoothPrivilegedPermission(service);
+
             return service.reportActivityInfo();
         }
 
@@ -1967,7 +2032,20 @@
             if (service == null) {
                 return false;
             }
-            return service.registerMetadataListener(listener, device);
+
+            if (service.mMetadataListeners == null) {
+                return false;
+            }
+            ArrayList<IBluetoothMetadataListener> list = service.mMetadataListeners.get(device);
+            if (list == null) {
+                list = new ArrayList<>();
+            } else if (list.contains(listener)) {
+                // The device is already registered with this listener
+                return true;
+            }
+            list.add(listener);
+            service.mMetadataListeners.put(device, list);
+            return true;
         }
 
         @Override
@@ -1976,7 +2054,14 @@
             if (service == null) {
                 return false;
             }
-            return service.unregisterMetadataListener(device);
+
+            if (service.mMetadataListeners == null) {
+                return false;
+            }
+            if (service.mMetadataListeners.containsKey(device)) {
+                service.mMetadataListeners.remove(device);
+            }
+            return true;
         }
 
         @Override
@@ -1985,7 +2070,11 @@
             if (service == null) {
                 return false;
             }
-            return service.setMetadata(device, key, value);
+
+            if (value.length > BluetoothDevice.METADATA_MAX_LENGTH) {
+                return false;
+            }
+            return service.mDatabaseManager.setCustomMeta(device, key, value);
         }
 
         @Override
@@ -1994,7 +2083,8 @@
             if (service == null) {
                 return null;
             }
-            return service.getMetadata(device, key);
+
+            return service.mDatabaseManager.getCustomMeta(device, key);
         }
 
         @Override
@@ -2010,7 +2100,8 @@
             if (service == null) {
                 return;
             }
-            service.onLeServiceUp();
+
+            service.mAdapterStateMachine.sendMessage(AdapterState.USER_TURN_ON);
         }
 
         @Override
@@ -2019,7 +2110,8 @@
             if (service == null) {
                 return;
             }
-            service.onBrEdrDown();
+
+            service.mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_OFF);
         }
 
         @Override
@@ -2029,6 +2121,9 @@
             if (service == null) {
                 return;
             }
+
+            enforceDumpPermission(service);
+
             service.dump(fd, writer, args);
             writer.close();
         }
@@ -2038,30 +2133,14 @@
 
     // ----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 boolean enable() {
-        return enable(false);
-    }
-
-    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");
@@ -2075,69 +2154,16 @@
     }
 
     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");
-
-        String addrString = null;
-        byte[] address = mAdapterProperties.getAddress();
-        return Utils.getAddressStringFromByte(address);
-    }
-
-    ParcelUuid[] getUuids() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        return mAdapterProperties.getUuids();
-    }
-
     public String getName() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        try {
-            return mAdapterProperties.getName();
-        } catch (Throwable t) {
-            debugLog("getName() - Unexpected exception (" + t + ")");
-        }
-        return null;
+        return mAdapterProperties.getName();
     }
 
-    boolean setName(String name) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
-        return mAdapterProperties.setName(name);
-    }
-
-    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());
-    }
-
-    private boolean validateInputOutputCapability(int capability) {
+    private static boolean isValidIoCapability(int capability) {
         if (capability < 0 || capability >= BluetoothAdapter.IO_CAPABILITY_MAX) {
             Log.e(TAG, "Invalid IO capability value - " + capability);
             return false;
@@ -2146,65 +2172,6 @@
         return true;
     }
 
-    int getIoCapability() {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
-        return mAdapterProperties.getIoCapability();
-    }
-
-    boolean setIoCapability(int capability) {
-        enforceCallingOrSelfPermission(
-                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
-        if (!validateInputOutputCapability(capability)) {
-            return false;
-        }
-
-        return mAdapterProperties.setIoCapability(capability);
-    }
-
-    int getLeIoCapability() {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
-        return mAdapterProperties.getLeIoCapability();
-    }
-
-    boolean setLeIoCapability(int capability) {
-        enforceCallingOrSelfPermission(
-                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
-        if (!validateInputOutputCapability(capability)) {
-            return false;
-        }
-
-        return mAdapterProperties.setLeIoCapability(capability);
-    }
-
-    int getScanMode() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        return mAdapterProperties.getScanMode();
-    }
-
-    boolean setScanMode(int mode, int duration) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        setDiscoverableTimeout(duration);
-
-        int newMode = convertScanModeToHal(mode);
-        return mAdapterProperties.setScanMode(newMode);
-    }
-
-    int getDiscoverableTimeout() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        return mAdapterProperties.getDiscoverableTimeout();
-    }
-
-    boolean setDiscoverableTimeout(int timeout) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        return mAdapterProperties.setDiscoverableTimeout(timeout);
-    }
-
     ArrayList<DiscoveringPackage> getDiscoveringPackages() {
         return mDiscoveringPackages;
     }
@@ -2215,10 +2182,9 @@
         }
     }
 
-    boolean startDiscovery(String callingPackage) {
+    boolean startDiscovery(String callingPackage, @Nullable String callingFeatureId) {
         UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());
         debugLog("startDiscovery");
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         boolean isQApp = Utils.isQApp(this, callingPackage);
         String permission = null;
@@ -2227,12 +2193,14 @@
         } else if (Utils.checkCallerHasNetworkSetupWizardPermission(this)) {
             permission = android.Manifest.permission.NETWORK_SETUP_WIZARD;
         } else if (isQApp) {
-            if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingUser)) {
+            if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingFeatureId,
+                    callingUser)) {
                 return false;
             }
             permission = android.Manifest.permission.ACCESS_FINE_LOCATION;
         } else {
-            if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingUser)) {
+            if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingFeatureId,
+                    callingUser)) {
                 return false;
             }
             permission = android.Manifest.permission.ACCESS_COARSE_LOCATION;
@@ -2244,32 +2212,12 @@
         return startDiscoveryNative();
     }
 
-    boolean cancelDiscovery() {
-        debugLog("cancelDiscovery");
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
-        return cancelDiscoveryNative();
-    }
-
-    boolean isDiscovering() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        return mAdapterProperties.isDiscovering();
-    }
-
-    long getDiscoveryEndMillis() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
-        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();
     }
 
@@ -2283,29 +2231,7 @@
         return mDatabaseManager;
     }
 
-    int getAdapterConnectionState() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mAdapterProperties.getConnectionState();
-    }
-
-    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");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
         if (deviceProp != null && deviceProp.getBondState() != BluetoothDevice.BOND_NONE) {
             return false;
@@ -2359,32 +2285,6 @@
         mBondStateMachine.sendMessage(msg);
     }
 
-    boolean cancelBondProcess(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-        byte[] addr = Utils.getBytesFromAddress(device.getAddress());
-
-        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp != null) {
-            deviceProp.setBondingInitiatedLocally(false);
-        }
-
-        return cancelBondNative(addr);
-    }
-
-    boolean removeBond(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDED) {
-            return false;
-        }
-        deviceProp.setBondingInitiatedLocally(false);
-
-        Message msg = mBondStateMachine.obtainMessage(BondStateMachine.REMOVE_BOND);
-        msg.obj = device;
-        mBondStateMachine.sendMessage(msg);
-        return true;
-    }
-
     /**
      * Get the bond state of a particular {@link BluetoothDevice}
      *
@@ -2396,7 +2296,6 @@
      */
     @VisibleForTesting
     public int getBondState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
         if (deviceProp == null) {
             return BluetoothDevice.BOND_NONE;
@@ -2404,23 +2303,63 @@
         return deviceProp.getBondState();
     }
 
-    boolean isBondingInitiatedLocally(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) {
-            return false;
-        }
-        return deviceProp.isBondingInitiatedLocally();
-    }
-
-    long getSupportedProfiles() {
-        return Config.getSupportedProfilesBitMask();
-    }
-
     int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        byte[] addr = Utils.getBytesFromAddress(device.getAddress());
-        return getConnectionStateNative(addr);
+        return getConnectionStateNative(addressToBytes(device.getAddress()));
+    }
+
+    /**
+     * Sets device as the active devices for the profiles passed into the function
+     *
+     * @param device is the remote bluetooth device
+     * @param profiles is a constant that references for which profiles we'll be setting the remote
+     *                 device as our active device. One of the following:
+     *                 {@link BluetoothAdapter#ACTIVE_DEVICE_AUDIO},
+     *                 {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL}
+     *                 {@link BluetoothAdapter#ACTIVE_DEVICE_ALL}
+     * @return false if profiles value is not one of the constants we accept, true otherwise
+     */
+    public boolean setActiveDevice(BluetoothDevice device, @ActiveDeviceUse int profiles) {
+        enforceCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
+
+        boolean setA2dp = false;
+        boolean setHeadset = false;
+
+        // Determine for which profiles we want to set device as our active device
+        switch(profiles) {
+            case BluetoothAdapter.ACTIVE_DEVICE_AUDIO:
+                setA2dp = true;
+                break;
+            case BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL:
+                setHeadset = true;
+                break;
+            case BluetoothAdapter.ACTIVE_DEVICE_ALL:
+                setA2dp = true;
+                setHeadset = true;
+                break;
+            default:
+                return false;
+        }
+
+        if (setA2dp && mA2dpService != null && mA2dpService.getConnectionPolicy(device)
+                == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            Log.i(TAG, "setActiveDevice: Setting active A2dp device " + device.getAddress());
+            mA2dpService.setActiveDevice(device);
+        }
+
+        if (mHearingAidService != null && mHearingAidService.getConnectionPolicy(device)
+                == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            Log.i(TAG, "setActiveDevice: Setting active Hearing Aid " + device.getAddress());
+            mHearingAidService.setActiveDevice(device);
+        }
+
+        if (setHeadset && mHeadsetService != null && mHeadsetService.getConnectionPolicy(device)
+                == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            Log.i(TAG, "setActiveDevice: Setting active Headset " + device.getAddress());
+            mHeadsetService.setActiveDevice(device);
+        }
+
+        return true;
     }
 
     /**
@@ -2430,7 +2369,6 @@
      * @return true if all profiles successfully connected, false if an error occurred
      */
     public boolean connectAllEnabledProfiles(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (!profileServicesRunning()) {
             Log.e(TAG, "connectAllEnabledProfiles: Not all profile services running");
             return false;
@@ -2443,70 +2381,67 @@
 
         int numProfilesConnected = 0;
         ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
-        ParcelUuid[] localDeviceUuids = getUuids();
+        ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
 
         // All profile toggles disabled, so connects all supported profiles
         if (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.A2DP, device)) {
-            mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
             Log.i(TAG, "connectAllEnabledProfiles: Connecting A2dp");
-            mA2dpService.connect(device);
+            // Set connection policy also connects the profile with CONNECTION_POLICY_ALLOWED
+            mA2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             numProfilesConnected++;
         }
         if (mA2dpSinkService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.A2DP_SINK, device)) {
-            mA2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
             Log.i(TAG, "connectAllEnabledProfiles: Connecting A2dp Sink");
-            mA2dpSinkService.connect(device);
+            mA2dpSinkService.setConnectionPolicy(device,
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             numProfilesConnected++;
         }
         if (mHeadsetService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HEADSET, device)) {
-            mHeadsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
             Log.i(TAG, "connectAllEnabledProfiles: Connecting Headset Profile");
-            mHeadsetService.connect(device);
+            mHeadsetService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             numProfilesConnected++;
         }
         if (mHeadsetClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HEADSET_CLIENT, device)) {
-            mHeadsetClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
             Log.i(TAG, "connectAllEnabledProfiles: Connecting HFP");
-            mHeadsetClientService.connect(device);
+            mHeadsetClientService.setConnectionPolicy(device,
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             numProfilesConnected++;
         }
         if (mMapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.MAP_CLIENT, device)) {
-            mMapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
             Log.i(TAG, "connectAllEnabledProfiles: Connecting MAP");
-            mMapClientService.connect(device);
+            mMapClientService.setConnectionPolicy(device,
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             numProfilesConnected++;
         }
         if (mHidHostService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HID_HOST, device)) {
-            mHidHostService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            Log.i(TAG,
-                    "connectAllEnabledProfiles: Connecting Hid Host Profile");
-            mHidHostService.connect(device);
+            Log.i(TAG, "connectAllEnabledProfiles: Connecting Hid Host Profile");
+            mHidHostService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             numProfilesConnected++;
         }
         if (mPanService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.PAN, device)) {
             Log.i(TAG, "connectAllEnabledProfiles: Connecting Pan Profile");
-            mPanService.connect(device);
+            mPanService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             numProfilesConnected++;
         }
         if (mPbapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.PBAP_CLIENT, device)) {
-            mPbapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
             Log.i(TAG, "connectAllEnabledProfiles: Connecting Pbap");
-            mPbapClientService.connect(device);
+            mPbapClientService.setConnectionPolicy(device,
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             numProfilesConnected++;
         }
         if (mHearingAidService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HEARING_AID, device)) {
-            Log.i(TAG,
-                    "connectAllEnabledProfiles: Connecting Hearing Aid Profile");
-            mHearingAidService.connect(device);
+            Log.i(TAG, "connectAllEnabledProfiles: Connecting Hearing Aid Profile");
+            mHearingAidService.setConnectionPolicy(device,
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             numProfilesConnected++;
         }
 
@@ -2523,76 +2458,53 @@
      * @return true if all profiles successfully disconnected, false if an error occurred
      */
     public boolean disconnectAllEnabledProfiles(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (!profileServicesRunning()) {
             Log.e(TAG, "disconnectAllEnabledProfiles: Not all profile services bound");
             return false;
         }
 
         ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
-        ParcelUuid[] localDeviceUuids = getUuids();
+        ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
 
         if (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.A2DP, device)) {
-            if (mA2dpService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
-                mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            }
-
             Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting A2dp");
             mA2dpService.disconnect(device);
         }
         if (mA2dpSinkService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.A2DP_SINK, device)) {
-            if (mA2dpSinkService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
-                mA2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            }
-
             Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting A2dp Sink");
             mA2dpSinkService.disconnect(device);
         }
         if (mHeadsetService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HEADSET, device)) {
-            if (mHeadsetService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
-                mHeadsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            }
-
             Log.i(TAG,
                     "disconnectAllEnabledProfiles: Disconnecting Headset Profile");
             mHeadsetService.disconnect(device);
         }
         if (mHeadsetClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HEADSET_CLIENT, device)) {
-            if (mHeadsetClientService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
-                mHeadsetClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            }
-
             Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting HFP");
             mHeadsetClientService.disconnect(device);
         }
         if (mMapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.MAP_CLIENT, device)) {
-            if (mMapClientService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
-                mMapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            }
-
-            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting MAP");
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting MAP Client");
             mMapClientService.disconnect(device);
         }
+        if (mMapService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.MAP, device)) {
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting MAP");
+            mMapService.disconnect(device);
+        }
         if (mHidDeviceService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HID_DEVICE, device)) {
-            Log.i(TAG,
-                    "disconnectAllEnabledProfiles: Disconnecting Hid Device "
-                            + "Profile");
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hid Device Profile");
             mHidDeviceService.disconnect(device);
         }
         if (mHidHostService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HID_HOST, device)) {
-            if (mHidHostService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
-                mHidHostService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            }
-
-            Log.i(TAG,
-                    "disconnectAllEnabledProfiles: Disconnecting Hid Host Profile");
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hid Host Profile");
             mHidHostService.disconnect(device);
         }
         if (mPanService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
@@ -2602,17 +2514,17 @@
         }
         if (mPbapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.PBAP_CLIENT, device)) {
-            if (mPbapClientService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
-                mPbapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
-            }
-
-            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pbap");
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pbap Client");
             mPbapClientService.disconnect(device);
         }
+        if (mPbapService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+                BluetoothProfile.PBAP, device)) {
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pbap Server");
+            mPbapService.disconnect(device);
+        }
         if (mHearingAidService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
                 BluetoothProfile.HEARING_AID, device)) {
-            Log.i(TAG,
-                    "disconnectAllEnabledProfiles: Disconnecting Hearing Aid Profile");
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hearing Aid Profile");
             mHearingAidService.disconnect(device);
         }
 
@@ -2626,7 +2538,6 @@
      * @return remote device name
      */
     public String getRemoteName(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (mRemoteDevices == null) {
             return null;
         }
@@ -2637,44 +2548,6 @@
         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;
-        }
-        return deviceProp.getDeviceType();
-    }
-
-    String getRemoteAlias(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        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;
-        }
-        deviceProp.setAlias(device, name);
-        return true;
-    }
-
-    int getRemoteClass(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) {
-            return 0;
-        }
-
-        return deviceProp.getBluetoothClass();
-    }
-
     /**
      * Get UUIDs for service supported by a remote device
      *
@@ -2683,7 +2556,6 @@
      */
     @VisibleForTesting
     public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
         if (deviceProp == null) {
             return null;
@@ -2691,101 +2563,26 @@
         return deviceProp.getUuids();
     }
 
-    boolean fetchRemoteUuids(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        mRemoteDevices.fetchUuids(device);
-        return true;
-    }
-
-    int getBatteryLevel(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null) {
-            return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
-        }
-        return deviceProp.getBatteryLevel();
-    }
-
-    boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
-        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)) {
-            return false;
-        }
-
-        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+    void logUserBondResponse(BluetoothDevice device, boolean accepted, int event) {
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
                 obfuscateAddress(device), 0, device.getType(),
                 BluetoothDevice.BOND_BONDING,
-                BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REPLIED,
-                accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
-        byte[] addr = Utils.getBytesFromAddress(device.getAddress());
-        return pinReplyNative(addr, accept, len, pinCode);
+                event,
+                accepted ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
     }
 
-    boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDING) {
-            return false;
-        }
-
-        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
-                obfuscateAddress(device), 0, device.getType(),
-                BluetoothDevice.BOND_BONDING,
-                BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED,
-                accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
-        byte[] addr = Utils.getBytesFromAddress(device.getAddress());
-        return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY, accept,
-                Utils.byteArrayToInt(passkey));
-    }
-
-    boolean setPairingConfirmation(BluetoothDevice device, boolean accept) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
-                "Need BLUETOOTH PRIVILEGED permission");
-        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
-        if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDING) {
-            return false;
-        }
-
-        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
-                obfuscateAddress(device), 0, device.getType(),
-                BluetoothDevice.BOND_BONDING,
-                BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED,
-                accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
-        byte[] addr = Utils.getBytesFromAddress(device.getAddress());
-        return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION, accept,
-                0);
-    }
-
-    int getPhonebookAccessPermission(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        SharedPreferences pref = getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
-                Context.MODE_PRIVATE);
-        if (!pref.contains(device.getAddress())) {
+    int getDeviceAccessFromPrefs(BluetoothDevice device, String prefFile) {
+        SharedPreferences prefs = getSharedPreferences(prefFile, Context.MODE_PRIVATE);
+        if (!prefs.contains(device.getAddress())) {
             return BluetoothDevice.ACCESS_UNKNOWN;
         }
-        return pref.getBoolean(device.getAddress(), false) ? BluetoothDevice.ACCESS_ALLOWED
+        return prefs.getBoolean(device.getAddress(), false)
+                ? BluetoothDevice.ACCESS_ALLOWED
                 : BluetoothDevice.ACCESS_REJECTED;
     }
 
-    boolean setSilenceMode(BluetoothDevice device, boolean silence) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
-                "Need BLUETOOTH PRIVILEGED permission");
-        mSilenceDeviceManager.setSilenceMode(device, silence);
-        return true;
-    }
-
-    boolean getSilenceMode(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
-                "Need BLUETOOTH PRIVILEGED permission");
-        return mSilenceDeviceManager.getSilenceMode(device);
-    }
-
-    boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
-        SharedPreferences pref = getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
-                Context.MODE_PRIVATE);
+    void setDeviceAccessFromPrefs(BluetoothDevice device, int value, String prefFile) {
+        SharedPreferences pref = getSharedPreferences(prefFile, Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = pref.edit();
         if (value == BluetoothDevice.ACCESS_UNKNOWN) {
             editor.remove(device.getAddress());
@@ -2793,149 +2590,55 @@
             editor.putBoolean(device.getAddress(), value == BluetoothDevice.ACCESS_ALLOWED);
         }
         editor.apply();
-        return true;
     }
 
-    int getMessageAccessPermission(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        SharedPreferences pref = getSharedPreferences(MESSAGE_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;
+    void setPhonebookAccessPermission(BluetoothDevice device, int value) {
+        setDeviceAccessFromPrefs(device, value, PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE);
     }
 
-    boolean setMessageAccessPermission(BluetoothDevice device, int value) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
-                "Need BLUETOOTH PRIVILEGED permission");
-        SharedPreferences pref = getSharedPreferences(MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE,
-                Context.MODE_PRIVATE);
-        SharedPreferences.Editor editor = pref.edit();
-        if (value == BluetoothDevice.ACCESS_UNKNOWN) {
-            editor.remove(device.getAddress());
-        } else {
-            editor.putBoolean(device.getAddress(), value == BluetoothDevice.ACCESS_ALLOWED);
-        }
-        editor.apply();
-        return true;
+    void setMessageAccessPermission(BluetoothDevice device, int value) {
+        setDeviceAccessFromPrefs(device, value, MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE);
     }
 
-    int getSimAccessPermission(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        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;
-    }
-
-    boolean setSimAccessPermission(BluetoothDevice device, int value) {
-        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());
-        } else {
-            editor.putBoolean(device.getAddress(), value == BluetoothDevice.ACCESS_ALLOWED);
-        }
-        editor.apply();
-        return true;
-    }
-
-    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;
-        }
-
-        mAdapterProperties.sendConnectionStateChange(device, profile, state, prevState);
-
-    }
-
-    IBluetoothSocketManager getSocketManager() {
-        return IBluetoothSocketManager.Stub.asInterface(mBluetoothSocketManagerBinder);
-    }
-
-    boolean factoryReset() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
-        if (mDatabaseManager != null) {
-            mDatabaseManager.factoryReset();
-        }
-        return factoryResetNative();
-    }
-
-    void registerCallback(IBluetoothCallback cb) {
-        mCallbacks.register(cb);
-    }
-
-    void unregisterCallback(IBluetoothCallback cb) {
-        mCallbacks.unregister(cb);
-    }
-
-    public int getNumOfAdvertisementInstancesSupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return mAdapterProperties.getNumOfAdvertisementInstancesSupported();
-    }
-
-    public boolean isMultiAdvertisementSupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        return getNumOfAdvertisementInstancesSupported() >= MIN_ADVT_INSTANCES_FOR_MA;
+    void setSimAccessPermission(BluetoothDevice device, int value) {
+        setDeviceAccessFromPrefs(device, value, SIM_ACCESS_PERMISSION_PREFERENCE_FILE);
     }
 
     public boolean isRpaOffloadSupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        enforceBluetoothPermission(this);
         return mAdapterProperties.isRpaOffloadSupported();
     }
 
     public int getNumOfOffloadedIrkSupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        enforceBluetoothPermission(this);
         return mAdapterProperties.getNumOfOffloadedIrkSupported();
     }
 
     public int getNumOfOffloadedScanFilterSupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.getNumOfOffloadedScanFilterSupported();
     }
 
     public int getOffloadedScanResultStorage() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.getOffloadedScanResultStorage();
     }
 
-    private boolean isActivityAndEnergyReportingSupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
-        return mAdapterProperties.isActivityAndEnergyReportingSupported();
-    }
-
     public boolean isLe2MPhySupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.isLe2MPhySupported();
     }
 
     public boolean isLeCodedPhySupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.isLeCodedPhySupported();
     }
 
     public boolean isLeExtendedAdvertisingSupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.isLeExtendedAdvertisingSupported();
     }
 
     public boolean isLePeriodicAdvertisingSupported() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.isLePeriodicAdvertisingSupported();
     }
 
     public int getLeMaximumAdvertisingDataLength() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.getLeMaximumAdvertisingDataLength();
     }
 
@@ -2945,7 +2648,6 @@
      * @return the maximum number of connected audio devices
      */
     public int getMaxConnectedAudioDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterProperties.getMaxConnectedAudioDevices();
     }
 
@@ -2955,12 +2657,10 @@
      * @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()) {
             return null;
@@ -3008,18 +2708,10 @@
     }
 
     public int getTotalNumOfTrackableAdvertisements() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        enforceBluetoothPermission(this);
         return mAdapterProperties.getTotalNumOfTrackableAdvertisements();
     }
 
-    void onLeServiceUp() {
-        mAdapterStateMachine.sendMessage(AdapterState.USER_TURN_ON);
-    }
-
-    void onBrEdrDown() {
-        mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_OFF);
-    }
-
     private static int convertScanModeToHal(int mode) {
         switch (mode) {
             case BluetoothAdapter.SCAN_MODE_NONE:
@@ -3118,7 +2810,7 @@
                     energyUsed = (long) (Math.addExact(Math.addExact(txMah, rxMah), idleMah)
                             * getOperatingVolt());
                 } catch (ArithmeticException e) {
-                    Slog.wtf(TAG, "overflow in bluetooth energy callback", e);
+                    Log.wtf(TAG, "overflow in bluetooth energy callback", e);
                     // Energy is already 0 if the exception was thrown.
                 }
             }
@@ -3137,7 +2829,7 @@
                 } 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.
-                    Slog.wtf(TAG, "overflow in bluetooth energy callback", e);
+                    Log.wtf(TAG, "overflow in bluetooth energy callback", e);
                     totalTxTimeMs = mTxTimeTotalMs;
                     totalRxTimeMs = mRxTimeTotalMs;
                     totalIdleTimeMs = mIdleTimeTotalMs;
@@ -3167,46 +2859,6 @@
                 + ctrlState + "traffic = " + Arrays.toString(data));
     }
 
-    boolean registerMetadataListener(IBluetoothMetadataListener listener,
-            BluetoothDevice device) {
-        if (mMetadataListeners == null) {
-            return false;
-        }
-
-        ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device);
-        if (list == null) {
-            list = new ArrayList<>();
-        } else if (list.contains(listener)) {
-            // The device is already registered with this listener
-            return true;
-        }
-        list.add(listener);
-        mMetadataListeners.put(device, list);
-        return true;
-    }
-
-    boolean unregisterMetadataListener(BluetoothDevice device) {
-        if (mMetadataListeners == null) {
-            return false;
-        }
-        if (mMetadataListeners.containsKey(device)) {
-            mMetadataListeners.remove(device);
-        }
-        return true;
-    }
-
-    boolean setMetadata(BluetoothDevice device, int key, byte[] value) {
-        if (value.length > BluetoothDevice.METADATA_MAX_LENGTH) {
-            Log.e(TAG, "setMetadata: value length too long " + value.length);
-            return false;
-        }
-        return mDatabaseManager.setCustomMeta(device, key, value);
-    }
-
-    byte[] getMetadata(BluetoothDevice device, int key) {
-        return mDatabaseManager.getCustomMeta(device, key);
-    }
-
     /**
      * Update metadata change to registered listeners
      */
@@ -3243,8 +2895,6 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
         if (args.length == 0) {
             writer.println("Skipping dump in APP SERVICES, see bluetooth_manager section.");
             writer.println("Use --print argument for dumpsys direct from AdapterService.");
diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java
index 94feef2..8596627 100644
--- a/src/com/android/bluetooth/btservice/AdapterState.java
+++ b/src/com/android/bluetooth/btservice/AdapterState.java
@@ -20,8 +20,8 @@
 import android.os.Message;
 import android.util.Log;
 
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 
 /**
  * This state machine handles Bluetooth Adapter State.
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 3320f0e..ca18140 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -26,8 +26,8 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.util.Log;
-import android.util.StatsLog;
 
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.a2dpsink.A2dpSinkService;
@@ -36,9 +36,9 @@
 import com.android.bluetooth.hfpclient.HeadsetClientService;
 import com.android.bluetooth.hid.HidHostService;
 import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -100,7 +100,7 @@
         return bsm;
     }
 
-    public void doQuit() {
+    public synchronized void doQuit() {
         quitNow();
     }
 
@@ -122,7 +122,7 @@
         }
 
         @Override
-        public boolean processMessage(Message msg) {
+        public synchronized boolean processMessage(Message msg) {
 
             BluetoothDevice dev = (BluetoothDevice) msg.obj;
 
@@ -178,7 +178,7 @@
         }
 
         @Override
-        public boolean processMessage(Message msg) {
+        public synchronized boolean processMessage(Message msg) {
             BluetoothDevice dev = (BluetoothDevice) msg.obj;
             DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
             boolean result = false;
@@ -323,14 +323,14 @@
             } else {
                 result = mAdapterService.createBondNative(addr, transport);
             }
-            StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+            BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
                     mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
                     BluetoothDevice.BOND_BONDING,
                     oobData == null ? BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN
                             : BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_OOB_DATA_PROVIDED,
                     BluetoothProtoEnums.UNBOND_REASON_UNKNOWN);
             if (!result) {
-                StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
                         mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
                         BluetoothDevice.BOND_NONE, BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN,
                         BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS);
@@ -386,12 +386,12 @@
         if (oldState == newState) {
             return;
         }
-        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
                 mAdapterService.obfuscateAddress(device), 0, device.getType(),
                 newState, BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN, reason);
         BluetoothClass deviceClass = device.getBluetoothClass();
         int classOfDevice = deviceClass == null ? 0 : deviceClass.getClassOfDevice();
-        StatsLog.write(StatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED,
                 mAdapterService.obfuscateAddress(device), classOfDevice);
         mAdapterProperties.onBondStateChanged(device, newState);
 
@@ -491,7 +491,7 @@
             device = Objects.requireNonNull(mRemoteDevices.getDevice(address));
         }
 
-        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
                 mAdapterService.obfuscateAddress(device), 0, device.getType(),
                 BluetoothDevice.BOND_BONDING,
                 BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REQUESTED, 0);
@@ -514,7 +514,7 @@
             bdDevice = Objects.requireNonNull(mRemoteDevices.getDevice(address));
         }
 
-        StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
                 mAdapterService.obfuscateAddress(bdDevice), 0, bdDevice.getType(),
                 BluetoothDevice.BOND_BONDING,
                 BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REQUESTED, 0);
@@ -538,22 +538,24 @@
         PbapClientService pbapClientService = PbapClientService.getPbapClientService();
 
         if (hidService != null) {
-            hidService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+            hidService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         }
         if (a2dpService != null) {
-            a2dpService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+            a2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         }
         if (headsetService != null) {
-            headsetService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+            headsetService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         }
         if (headsetClientService != null) {
-            headsetClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+            headsetClientService.setConnectionPolicy(device,
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         }
         if (a2dpSinkService != null) {
-            a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+            a2dpSinkService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         }
         if (pbapClientService != null) {
-            pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+            pbapClientService.setConnectionPolicy(device,
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         }
     }
 
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 0ec5b47..f38b5ff 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -20,8 +20,9 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.SystemProperties;
 import android.provider.Settings;
-import android.util.FeatureFlagUtils;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.bluetooth.R;
@@ -117,8 +118,7 @@
         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)) {
+            if (!supported && (config.mClass == HearingAidService.class) && isHearingAidSettingsEnabled(ctx)) {
                 Log.v(TAG, "Feature Flag enables support for HearingAidService");
                 supported = true;
             }
@@ -160,4 +160,30 @@
 
         return (disabledProfilesBitMask & profileMask) != 0;
     }
+
+    private static boolean isHearingAidSettingsEnabled(Context context) {
+        final String flagOverridePrefix = "sys.fflag.override.";
+        final String hearingAidSettings = "settings_bluetooth_hearing_aid";
+
+        // Override precedence:
+        // Settings.Global -> sys.fflag.override.* -> static list
+
+        // Step 1: check if hearing aid flag is set in Settings.Global.
+        String value;
+        if (context != null) {
+            value = Settings.Global.getString(context.getContentResolver(), hearingAidSettings);
+            if (!TextUtils.isEmpty(value)) {
+                return Boolean.parseBoolean(value);
+            }
+        }
+
+        // Step 2: check if hearing aid flag has any override.
+        value = SystemProperties.get(flagOverridePrefix + hearingAidSettings);
+        if (!TextUtils.isEmpty(value)) {
+            return Boolean.parseBoolean(value);
+        }
+
+        // Step 3: return default value.
+        return false;
+    }
 }
diff --git a/src/com/android/bluetooth/btservice/PhonePolicy.java b/src/com/android/bluetooth/btservice/PhonePolicy.java
index 39b1252..adc69cd 100644
--- a/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.BroadcastReceiver;
@@ -40,6 +41,7 @@
 import com.android.bluetooth.pan.PanService;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 
 import java.util.HashSet;
 import java.util.List;
@@ -79,6 +81,7 @@
     private static final int MESSAGE_CONNECT_OTHER_PROFILES = 3;
     private static final int MESSAGE_ADAPTER_STATE_TURNED_ON = 4;
     private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
+    private static final int MESSAGE_DEVICE_CONNECTED = 6;
 
     // Timeouts
     @VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s
@@ -115,6 +118,16 @@
                             BluetoothProfile.A2DP, -1, // No-op argument
                             intent).sendToTarget();
                     break;
+                case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
+                            BluetoothProfile.HEADSET, -1, // No-op argument
+                            intent).sendToTarget();
+                    break;
+                case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
+                            BluetoothProfile.HEARING_AID, -1, // No-op argument
+                            intent).sendToTarget();
+                    break;
                 case BluetoothAdapter.ACTION_STATE_CHANGED:
                     // Only pass the message on if the adapter has actually changed state from
                     // non-ON to ON. NOTE: ON is the state depicting BREDR ON and not just BLE ON.
@@ -126,6 +139,8 @@
                 case BluetoothDevice.ACTION_UUID:
                     mHandler.obtainMessage(MESSAGE_PROFILE_INIT_PRIORITIES, intent).sendToTarget();
                     break;
+                case BluetoothDevice.ACTION_ACL_CONNECTED:
+                    mHandler.obtainMessage(MESSAGE_DEVICE_CONNECTED, intent).sendToTarget();
                 default:
                     Log.e(TAG, "Received unexpected intent, action=" + action);
                     break;
@@ -178,7 +193,7 @@
                     Intent intent = (Intent) msg.obj;
                     BluetoothDevice activeDevice =
                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                    processProfileActiveDeviceChanged(activeDevice, msg.arg1);
+                    processActiveDeviceChanged(activeDevice, msg.arg1);
                 }
                 break;
 
@@ -195,6 +210,11 @@
                     resetStates();
                     autoConnect();
                     break;
+                case MESSAGE_DEVICE_CONNECTED:
+                    Intent intent = (Intent) msg.obj;
+                    BluetoothDevice device =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    processDeviceConnected(device);
             }
         }
     }
@@ -206,9 +226,12 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
         filter.addAction(BluetoothDevice.ACTION_UUID);
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+        filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
         mAdapterService.registerReceiver(mReceiver, filter);
     }
 
@@ -234,37 +257,42 @@
 
         // 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)) {
-            hidService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        if ((hidService != null) && (ArrayUtils.contains(uuids, BluetoothUuid.HID)
+                || ArrayUtils.contains(uuids, BluetoothUuid.HOGP)) && (
+                hidService.getConnectionPolicy(device)
+                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
+            hidService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         }
 
         // 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))) {
-            headsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+        if ((headsetService != null) && ((ArrayUtils.contains(uuids, BluetoothUuid.HSP)
+                || ArrayUtils.contains(uuids, BluetoothUuid.HFP)) && (
+                headsetService.getConnectionPolicy(device)
+                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN))) {
+            headsetService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         }
 
-        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 ((a2dpService != null) && (ArrayUtils.contains(uuids, BluetoothUuid.A2DP_SINK)
+                || ArrayUtils.contains(uuids, BluetoothUuid.ADV_AUDIO_DIST)) && (
+                a2dpService.getConnectionPolicy(device)
+                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
+            a2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         }
 
-        if ((panService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU) && (
-                panService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)
+        if ((panService != null) && (ArrayUtils.contains(uuids, BluetoothUuid.PANU) && (
+                panService.getConnectionPolicy(device)
+                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)
                 && mAdapterService.getResources()
                 .getBoolean(R.bool.config_bluetooth_pan_enable_autoconnect))) {
-            panService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            panService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         }
 
-        if ((hearingAidService != null) && BluetoothUuid.isUuidPresent(uuids,
-                BluetoothUuid.HearingAid) && (hearingAidService.getPriority(device)
-                == BluetoothProfile.PRIORITY_UNDEFINED)) {
+        if ((hearingAidService != null) && ArrayUtils.contains(uuids,
+                BluetoothUuid.HEARING_AID) && (hearingAidService.getConnectionPolicy(device)
+                == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
             debugLog("setting hearing aid profile priority for device " + device);
-            hearingAidService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            hearingAidService.setConnectionPolicy(device,
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         }
     }
 
@@ -285,50 +313,33 @@
                 connectOtherProfile(device);
             }
             if (nextState == BluetoothProfile.STATE_DISCONNECTED) {
-                handleAllProfilesDisconnected(device);
-                if (prevState == BluetoothProfile.STATE_CONNECTING) {
-                    HeadsetService hsService = mFactory.getHeadsetService();
-                    boolean hsDisconnected = hsService == null
-                            || hsService.getConnectionState(device)
-                            == BluetoothProfile.STATE_DISCONNECTED;
-                    A2dpService a2dpService = mFactory.getA2dpService();
-                    boolean a2dpDisconnected = a2dpService == null
-                            || a2dpService.getConnectionState(device)
-                            == BluetoothProfile.STATE_DISCONNECTED;
-                    debugLog("processProfileStateChanged, device=" + device + ", a2dpDisconnected="
-                            + a2dpDisconnected + ", hsDisconnected=" + hsDisconnected);
-                    if (hsDisconnected && a2dpDisconnected) {
-                        removeAutoConnectFromA2dpSink(device);
-                        removeAutoConnectFromHeadset(device);
-                    }
+                if (profileId == BluetoothProfile.A2DP) {
+                    mAdapterService.getDatabase().setDisconnection(device);
                 }
+                handleAllProfilesDisconnected(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;
+    /**
+     * Updates the last connection date in the connection order database for the newly active device
+     * if connected to a2dp profile
+     *
+     * @param device is the device we just made the active device
+     */
+    private void processActiveDeviceChanged(BluetoothDevice device, int profileId) {
+        debugLog("processActiveDeviceChanged, device=" + device + ", profile=" + profileId);
+
+        if (device != null) {
+            mAdapterService.getDatabase().setConnection(device, profileId == BluetoothProfile.A2DP);
         }
     }
 
+    private void processDeviceConnected(BluetoothDevice device) {
+        debugLog("processDeviceConnected, device=" + device);
+        mAdapterService.getDatabase().setConnection(device, false);
+    }
+
     private boolean handleAllProfilesDisconnected(BluetoothDevice device) {
         boolean atLeastOneProfileConnectedForDevice = false;
         boolean allProfilesEmpty = true;
@@ -381,15 +392,16 @@
 
         if (!mAdapterService.isQuietModeEnabled()) {
             debugLog("autoConnect: Initiate auto connection on BT on...");
-            final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
-            if (bondedDevices == null) {
-                errorLog("autoConnect: bondedDevices are null");
+            final BluetoothDevice mostRecentlyActiveA2dpDevice =
+                    mAdapterService.getDatabase().getMostRecentlyConnectedA2dpDevice();
+            if (mostRecentlyActiveA2dpDevice == null) {
+                errorLog("autoConnect: most recently active a2dp device is null");
                 return;
             }
-            for (BluetoothDevice device : bondedDevices) {
-                autoConnectHeadset(device);
-                autoConnectA2dp(device);
-            }
+            debugLog("autoConnect: Device " + mostRecentlyActiveA2dpDevice
+                    + " attempting auto connection");
+            autoConnectHeadset(mostRecentlyActiveA2dpDevice);
+            autoConnectA2dp(mostRecentlyActiveA2dpDevice);
         } else {
             debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections");
         }
@@ -401,13 +413,13 @@
             warnLog("autoConnectA2dp: service is null, failed to connect to " + device);
             return;
         }
-        int a2dpPriority = a2dpService.getPriority(device);
-        if (a2dpPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+        int a2dpConnectionPolicy = a2dpService.getConnectionPolicy(device);
+        if (a2dpConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             debugLog("autoConnectA2dp: connecting A2DP with " + device);
             a2dpService.connect(device);
         } else {
             debugLog("autoConnectA2dp: skipped auto-connect A2DP with device " + device
-                    + " priority " + a2dpPriority);
+                    + " connectionPolicy " + a2dpConnectionPolicy);
         }
     }
 
@@ -417,13 +429,13 @@
             warnLog("autoConnectHeadset: service is null, failed to connect to " + device);
             return;
         }
-        int headsetPriority = hsService.getPriority(device);
-        if (headsetPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+        int headsetConnectionPolicy = hsService.getConnectionPolicy(device);
+        if (headsetConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             debugLog("autoConnectHeadset: Connecting HFP with " + device);
             hsService.connect(device);
         } else {
             debugLog("autoConnectHeadset: skipped auto-connect HFP with device " + device
-                    + " priority " + headsetPriority);
+                    + " connectionPolicy " + headsetConnectionPolicy);
         }
     }
 
@@ -462,8 +474,9 @@
         PanService panService = mFactory.getPanService();
 
         if (hsService != null) {
-            if (!mHeadsetRetrySet.contains(device) && (hsService.getPriority(device)
-                    >= BluetoothProfile.PRIORITY_ON) && (hsService.getConnectionState(device)
+            if (!mHeadsetRetrySet.contains(device) && (hsService.getConnectionPolicy(device)
+                    == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+                    && (hsService.getConnectionState(device)
                     == BluetoothProfile.STATE_DISCONNECTED)) {
                 debugLog("Retrying connection to Headset with device " + device);
                 mHeadsetRetrySet.add(device);
@@ -471,8 +484,9 @@
             }
         }
         if (a2dpService != null) {
-            if (!mA2dpRetrySet.contains(device) && (a2dpService.getPriority(device)
-                    >= BluetoothProfile.PRIORITY_ON) && (a2dpService.getConnectionState(device)
+            if (!mA2dpRetrySet.contains(device) && (a2dpService.getConnectionPolicy(device)
+                    == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+                    && (a2dpService.getConnectionState(device)
                     == BluetoothProfile.STATE_DISCONNECTED)) {
                 debugLog("Retrying connection to A2DP with device " + device);
                 mA2dpRetrySet.add(device);
@@ -483,8 +497,9 @@
             List<BluetoothDevice> panConnDevList = panService.getConnectedDevices();
             // TODO: the panConnDevList.isEmpty() check below should be removed once
             // Multi-PAN is supported.
-            if (panConnDevList.isEmpty() && (panService.getPriority(device)
-                    >= BluetoothProfile.PRIORITY_ON) && (panService.getConnectionState(device)
+            if (panConnDevList.isEmpty() && (panService.getConnectionPolicy(device)
+                    == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+                    && (panService.getConnectionState(device)
                     == BluetoothProfile.STATE_DISCONNECTED)) {
                 debugLog("Retrying connection to PAN with device " + device);
                 panService.connect(device);
@@ -492,77 +507,6 @@
         }
     }
 
-    /**
-     * 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);
-        }
-    }
-
-    /**
-     * 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);
-        }
-    }
-
-    /**
-     * 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.i(TAG, msg);
diff --git a/src/com/android/bluetooth/btservice/ProfileObserver.java b/src/com/android/bluetooth/btservice/ProfileObserver.java
deleted file mode 100644
index d5ddff4..0000000
--- a/src/com/android/bluetooth/btservice/ProfileObserver.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.android.bluetooth.btservice;
-
-import android.bluetooth.BluetoothAdapter;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.Settings;
-
-/**
- * This helper class monitors the state of the enabled profiles and will update and restart
- * the adapter when necessary.
- */
-public class ProfileObserver extends ContentObserver {
-    private Context mContext;
-    private AdapterService mService;
-    private AdapterStateObserver mStateObserver;
-
-    public ProfileObserver(Context context, AdapterService service, Handler handler) {
-        super(handler);
-        mContext = context;
-        mService = service;
-        mStateObserver = new AdapterStateObserver(this);
-    }
-
-    public void start() {
-        mContext.getContentResolver()
-                .registerContentObserver(
-                        Settings.Global.getUriFor(Settings.Global.BLUETOOTH_DISABLED_PROFILES),
-                        false, this);
-    }
-
-    private void onBluetoothOff() {
-        mContext.unregisterReceiver(mStateObserver);
-        Config.init(mContext);
-        mService.enable();
-    }
-
-    public void stop() {
-        mContext.getContentResolver().unregisterContentObserver(this);
-    }
-
-    @Override
-    public void onChange(boolean selfChange) {
-        if (mService.isEnabled()) {
-            mContext.registerReceiver(mStateObserver,
-                    new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
-            mService.disable();
-        }
-    }
-
-    private static class AdapterStateObserver extends BroadcastReceiver {
-        private ProfileObserver mProfileObserver;
-
-        AdapterStateObserver(ProfileObserver observer) {
-            mProfileObserver = observer;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())
-                    && intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
-                    == BluetoothAdapter.STATE_OFF) {
-                mProfileObserver.onBluetoothOff();
-            }
-        }
-    }
-}
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 2965715..abbb677 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -31,8 +31,8 @@
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.util.Log;
-import android.util.StatsLog;
 
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.hfp.HeadsetHalConstants;
@@ -311,10 +311,11 @@
         /**
          * @param mBondState the mBondState to set
          */
-        void setBondState(int mBondState) {
+        void setBondState(int newBondState) {
             synchronized (mObject) {
-                this.mBondState = mBondState;
-                if (mBondState == BluetoothDevice.BOND_NONE) {
+                if ((mBondState == BluetoothDevice.BOND_BONDED
+                        && newBondState == BluetoothDevice.BOND_BONDING)
+                        || newBondState == 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.
@@ -322,6 +323,7 @@
                     mUuids = null;
                     mAlias = null;
                 }
+                mBondState = newBondState;
             }
         }
 
@@ -334,6 +336,14 @@
             }
         }
 
+        boolean isBonding() {
+            return getBondState() == BluetoothDevice.BOND_BONDING;
+        }
+
+        boolean isBondingOrBonded() {
+            return isBonding() || getBondState() == BluetoothDevice.BOND_BONDED;
+        }
+
         /**
          * @param isBondingInitiatedLocally wether bonding is initiated locally
          */
@@ -642,11 +652,11 @@
 
         int connectionState = newState == AbstractionLayer.BT_ACL_STATE_CONNECTED
                 ? BluetoothAdapter.STATE_CONNECTED : BluetoothAdapter.STATE_DISCONNECTED;
-        StatsLog.write(StatsLog.BLUETOOTH_ACL_CONNECTION_STATE_CHANGED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACL_CONNECTION_STATE_CHANGED,
                 sAdapterService.obfuscateAddress(device), connectionState);
         BluetoothClass deviceClass = device.getBluetoothClass();
         int classOfDevice = deviceClass == null ? 0 : deviceClass.getClassOfDevice();
-        StatsLog.write(StatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED,
                 sAdapterService.obfuscateAddress(device), classOfDevice);
 
         if (intent != null) {
diff --git a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index f86adbd..1eb3762 100644
--- a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -17,6 +17,8 @@
 package com.android.bluetooth.btservice.storage;
 
 import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -33,8 +35,8 @@
 import android.os.Message;
 import android.provider.Settings;
 import android.util.Log;
-import android.util.StatsLog;
 
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.internal.annotations.VisibleForTesting;
@@ -42,9 +44,11 @@
 import com.google.common.collect.EvictingQueue;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Semaphore;
@@ -76,6 +80,33 @@
     private static final int MSG_CLEAR_DATABASE = 100;
     private static final String LOCAL_STORAGE = "LocalStorage";
 
+    private static final String
+            LEGACY_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
+    private static final String
+            LEGACY_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
+    private static final String
+            LEGACY_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_";
+    private static final String
+            LEGACY_A2DP_SRC_PRIORITY_PREFIX = "bluetooth_a2dp_src_priority_";
+    private static final String LEGACY_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX =
+            "bluetooth_a2dp_supports_optional_codecs_";
+    private static final String LEGACY_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX =
+            "bluetooth_a2dp_optional_codecs_enabled_";
+    private static final String
+            LEGACY_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_";
+    private static final String
+            LEGACY_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_";
+    private static final String
+            LEGACY_MAP_CLIENT_PRIORITY_PREFIX = "bluetooth_map_client_priority_";
+    private static final String
+            LEGACY_PBAP_CLIENT_PRIORITY_PREFIX = "bluetooth_pbap_client_priority_";
+    private static final String
+            LEGACY_SAP_PRIORITY_PREFIX = "bluetooth_sap_priority_";
+    private static final String
+            LEGACY_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_";
+    private static final String
+            LEGACY_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_";
+
     /**
      * Constructor of the DatabaseManager
      */
@@ -103,6 +134,7 @@
                                     .createDatabaseWithoutMigration(mAdapterService);
                             list = mDatabase.load();
                         }
+                        compactLastConnectionTime(list);
                         cacheMetadata(list);
                     }
                     break;
@@ -170,7 +202,7 @@
                 if (mMetadataCache.containsKey(address)) {
                     return;
                 }
-                createMetadata(address);
+                createMetadata(address, false);
             } else {
                 Metadata metadata = mMetadataCache.get(address);
                 if (metadata != null) {
@@ -223,7 +255,7 @@
 
             String address = device.getAddress();
             if (!mMetadataCache.containsKey(address)) {
-                createMetadata(address);
+                createMetadata(address, false);
             }
             Metadata data = mMetadataCache.get(address);
             byte[] oldValue = data.getCustomizedMeta(key);
@@ -269,7 +301,7 @@
     }
 
     /**
-     * Set the device profile prioirty
+     * Set the device profile connection policy
      *
      * @param device {@link BluetoothDevice} wish to set
      * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
@@ -279,53 +311,53 @@
      * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
      * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
      * {@link BluetoothProfile#HEARING_AID}
-     * @param newPriority the priority to set; one of
-     * {@link BluetoothProfile#PRIORITY_UNDEFINED},
-     * {@link BluetoothProfile#PRIORITY_OFF},
-     * {@link BluetoothProfile#PRIORITY_ON},
-     * {@link BluetoothProfile#PRIORITY_AUTO_CONNECT}
+     * @param newConnectionPolicy the connectionPolicy to set; one of
+     * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
+     * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED}
      */
     @VisibleForTesting
-    public boolean setProfilePriority(BluetoothDevice device, int profile, int newPriority) {
+    public boolean setProfileConnectionPolicy(BluetoothDevice device, int profile,
+            int newConnectionPolicy) {
         synchronized (mMetadataCache) {
             if (device == null) {
-                Log.e(TAG, "setProfilePriority: device is null");
+                Log.e(TAG, "setProfileConnectionPolicy: device is null");
                 return false;
             }
 
-            if (newPriority != BluetoothProfile.PRIORITY_UNDEFINED
-                    && newPriority != BluetoothProfile.PRIORITY_OFF
-                    && newPriority != BluetoothProfile.PRIORITY_ON
-                    && newPriority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
-                Log.e(TAG, "setProfilePriority: invalid priority " + newPriority);
+            if (newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+                    && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                Log.e(TAG, "setProfileConnectionPolicy: invalid connection policy "
+                        + newConnectionPolicy);
                 return false;
             }
 
             String address = device.getAddress();
             if (!mMetadataCache.containsKey(address)) {
-                if (newPriority == BluetoothProfile.PRIORITY_UNDEFINED) {
+                if (newConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_UNKNOWN) {
                     return true;
                 }
-                createMetadata(address);
+                createMetadata(address, false);
             }
             Metadata data = mMetadataCache.get(address);
-            int oldPriority = data.getProfilePriority(profile);
-            if (oldPriority == newPriority) {
-                Log.v(TAG, "setProfilePriority priority not changed.");
+            int oldConnectionPolicy = data.getProfileConnectionPolicy(profile);
+            if (oldConnectionPolicy == newConnectionPolicy) {
+                Log.v(TAG, "setProfileConnectionPolicy connection policy not changed.");
                 return true;
             }
             String profileStr = BluetoothProfile.getProfileName(profile);
-            logMetadataChange(address, profileStr + " priority changed: "
-                    + ": " + oldPriority + " -> " + newPriority);
+            logMetadataChange(address, profileStr + " connection policy changed: "
+                    + ": " + oldConnectionPolicy + " -> " + newConnectionPolicy);
 
-            data.setProfilePriority(profile, newPriority);
+            data.setProfileConnectionPolicy(profile, newConnectionPolicy);
             updateDatabase(data);
             return true;
         }
     }
 
     /**
-     * Get the device profile prioirty
+     * Get the device profile connection policy
      *
      * @param device {@link BluetoothDevice} wish to get
      * @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
@@ -335,32 +367,32 @@
      * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
      * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
      * {@link BluetoothProfile#HEARING_AID}
-     * @return the profile priority of the device; one of
-     * {@link BluetoothProfile#PRIORITY_UNDEFINED},
-     * {@link BluetoothProfile#PRIORITY_OFF},
-     * {@link BluetoothProfile#PRIORITY_ON},
-     * {@link BluetoothProfile#PRIORITY_AUTO_CONNECT}
+     * @return the profile connection policy of the device; one of
+     * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
+     * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED}
      */
     @VisibleForTesting
-    public int getProfilePriority(BluetoothDevice device, int profile) {
+    public int getProfileConnectionPolicy(BluetoothDevice device, int profile) {
         synchronized (mMetadataCache) {
             if (device == null) {
-                Log.e(TAG, "getProfilePriority: device is null");
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                Log.e(TAG, "getProfileConnectionPolicy: device is null");
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
 
             String address = device.getAddress();
 
             if (!mMetadataCache.containsKey(address)) {
-                Log.d(TAG, "getProfilePriority: device " + address + " is not in cache");
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                Log.d(TAG, "getProfileConnectionPolicy: device " + address + " is not in cache");
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
 
             Metadata data = mMetadataCache.get(address);
-            int priority = data.getProfilePriority(profile);
-            Log.v(TAG, "getProfilePriority: " + address + ", profile=" + profile
-                    + ", priority = " + priority);
-            return priority;
+            int connectionPolicy = data.getProfileConnectionPolicy(profile);
+
+            Log.v(TAG, "getProfileConnectionPolicy: " + address + ", profile=" + profile
+                    + ", connectionPolicy = " + connectionPolicy);
+            return connectionPolicy;
         }
     }
 
@@ -415,6 +447,7 @@
      * {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED},
      */
     @VisibleForTesting
+    @OptionalCodecsSupportStatus
     public int getA2dpSupportsOptionalCodecs(BluetoothDevice device) {
         synchronized (mMetadataCache) {
             if (device == null) {
@@ -485,6 +518,7 @@
      * {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED}
      */
     @VisibleForTesting
+    @OptionalCodecsPreferenceStatus
     public int getA2dpOptionalCodecsEnabled(BluetoothDevice device) {
         synchronized (mMetadataCache) {
             if (device == null) {
@@ -505,6 +539,159 @@
     }
 
     /**
+     * Updates the time this device was last connected
+     *
+     * @param device is the remote bluetooth device for which we are setting the connection time
+     */
+    public void setConnection(BluetoothDevice device, boolean isA2dpDevice) {
+        synchronized (mMetadataCache) {
+            Log.d(TAG, "setConnection: device=" + device + " and isA2dpDevice=" + isA2dpDevice);
+            if (device == null) {
+                Log.e(TAG, "setConnection: device is null");
+                return;
+            }
+
+            if (isA2dpDevice) {
+                resetActiveA2dpDevice();
+            }
+
+            String address = device.getAddress();
+
+            if (!mMetadataCache.containsKey(address)) {
+                Log.d(TAG, "setConnection: Creating new metadata entry for device: " + device);
+                createMetadata(address, isA2dpDevice);
+                return;
+            }
+            // Updates last_active_time to the current counter value and increments the counter
+            Metadata metadata = mMetadataCache.get(address);
+            metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
+
+            // Only update is_active_a2dp_device if an a2dp device is connected
+            if (isA2dpDevice) {
+                metadata.is_active_a2dp_device = true;
+            }
+
+            Log.d(TAG, "Updating last connected time for device: " + device + " to "
+                    + metadata.last_active_time);
+            updateDatabase(metadata);
+        }
+    }
+
+    /**
+     * Sets is_active_device to false if currently true for device
+     *
+     * @param device is the remote bluetooth device with which we have disconnected a2dp
+     */
+    public void setDisconnection(BluetoothDevice device) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "setDisconnection: device is null");
+                return;
+            }
+
+            String address = device.getAddress();
+
+            if (!mMetadataCache.containsKey(address)) {
+                return;
+            }
+            // Updates last connected time to either current time if connected or -1 if disconnected
+            Metadata metadata = mMetadataCache.get(address);
+            if (metadata.is_active_a2dp_device) {
+                metadata.is_active_a2dp_device = false;
+                Log.d(TAG, "setDisconnection: Updating is_active_device to false for device: "
+                        + device);
+                updateDatabase(metadata);
+            }
+        }
+    }
+
+    /**
+     * Remove a2dpActiveDevice from the current active device in the connection order table
+     */
+    private void resetActiveA2dpDevice() {
+        synchronized (mMetadataCache) {
+            Log.d(TAG, "resetActiveA2dpDevice()");
+            for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
+                Metadata metadata = entry.getValue();
+                if (metadata.is_active_a2dp_device) {
+                    Log.d(TAG, "resetActiveA2dpDevice");
+                    metadata.is_active_a2dp_device = false;
+                    updateDatabase(metadata);
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the most recently connected bluetooth devices in order with most recently connected
+     * first and least recently connected last
+     *
+     * @return a {@link List} of {@link BluetoothDevice} representing connected bluetooth devices
+     * in order of most recently connected
+     */
+    public List<BluetoothDevice> getMostRecentlyConnectedDevices() {
+        List<BluetoothDevice> mostRecentlyConnectedDevices = new ArrayList<>();
+        synchronized (mMetadataCache) {
+            List<Metadata> sortedMetadata = new ArrayList<>(mMetadataCache.values());
+            sortedMetadata.sort((o1, o2) -> Long.compare(o2.last_active_time, o1.last_active_time));
+            for (Metadata metadata : sortedMetadata) {
+                try {
+                    mostRecentlyConnectedDevices.add(BluetoothAdapter.getDefaultAdapter()
+                            .getRemoteDevice(metadata.getAddress()));
+                } catch (IllegalArgumentException ex) {
+                    Log.d(TAG, "getBondedDevicesOrdered: Invalid address for "
+                            + "device " + metadata.getAddress());
+                }
+            }
+        }
+        return mostRecentlyConnectedDevices;
+    }
+
+    /**
+     * Gets the last active a2dp device
+     *
+     * @return the most recently active a2dp device or null if the last a2dp device was null
+     */
+    public BluetoothDevice getMostRecentlyConnectedA2dpDevice() {
+        synchronized (mMetadataCache) {
+            for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
+                Metadata metadata = entry.getValue();
+                if (metadata.is_active_a2dp_device) {
+                    try {
+                        return BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+                                metadata.getAddress());
+                    } catch (IllegalArgumentException ex) {
+                        Log.d(TAG, "getMostRecentlyConnectedA2dpDevice: Invalid address for "
+                                + "device " + metadata.getAddress());
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     *
+     * @param metadataList is the list of metadata
+     */
+    private void compactLastConnectionTime(List<Metadata> metadataList) {
+        Log.d(TAG, "compactLastConnectionTime: Compacting metadata after load");
+        MetadataDatabase.sCurrentConnectionNumber = 0;
+        // Have to go in reverse order as list is ordered by descending last_active_time
+        for (int index = metadataList.size() - 1; index >= 0; index--) {
+            Metadata metadata = metadataList.get(index);
+            if (metadata.last_active_time != MetadataDatabase.sCurrentConnectionNumber) {
+                Log.d(TAG, "compactLastConnectionTime: Setting last_active_item for device: "
+                        + metadata.getAddress() + " from " + metadata.last_active_time + " to "
+                        + MetadataDatabase.sCurrentConnectionNumber);
+                metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber;
+                updateDatabase(metadata);
+                MetadataDatabase.sCurrentConnectionNumber++;
+            }
+        }
+    }
+
+    /**
      * Get the {@link Looper} for the handler thread. This is used in testing and helper
      * objects
      *
@@ -577,8 +764,9 @@
         mMetadataCache.clear();
     }
 
-    void createMetadata(String address) {
+    void createMetadata(String address, boolean isActiveA2dpDevice) {
         Metadata data = new Metadata(address);
+        data.is_active_a2dp_device = isActiveA2dpDevice;
         mMetadataCache.put(address, data);
         updateDatabase(data);
         logMetadataChange(address, "Metadata created");
@@ -641,63 +829,66 @@
         ContentResolver contentResolver = mAdapterService.getContentResolver();
 
         for (BluetoothDevice device : bondedDevices) {
-            int a2dpPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int a2dpSinkPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int hearingaidPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothHearingAidPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int headsetPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int headsetClientPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int hidHostPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothHidHostPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int mapPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int mapClientPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int panPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothPanPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int pbapPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int pbapClientPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
-            int sapPriority = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
-                    BluetoothProfile.PRIORITY_UNDEFINED);
+            int a2dpConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyA2dpSinkPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int a2dpSinkConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyA2dpSrcPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int hearingaidConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyHearingAidPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int headsetConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyHeadsetPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int headsetClientConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyHeadsetPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int hidHostConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyHidHostPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int mapConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyMapPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int mapClientConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyMapClientPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int panConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyPanPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int pbapConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyPbapClientPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int pbapClientConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacyPbapClientPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+            int sapConnectionPolicy = Settings.Global.getInt(contentResolver,
+                    getLegacySapPriorityKey(device.getAddress()),
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
             int a2dpSupportsOptionalCodec = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
+                    getLegacyA2dpSupportsOptionalCodecsKey(device.getAddress()),
                     BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
             int a2dpOptionalCodecEnabled = Settings.Global.getInt(contentResolver,
-                    Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
+                    getLegacyA2dpOptionalCodecsEnabledKey(device.getAddress()),
                     BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
 
             String address = device.getAddress();
             Metadata data = new Metadata(address);
-            data.setProfilePriority(BluetoothProfile.A2DP, a2dpPriority);
-            data.setProfilePriority(BluetoothProfile.A2DP_SINK, a2dpSinkPriority);
-            data.setProfilePriority(BluetoothProfile.HEADSET, headsetPriority);
-            data.setProfilePriority(BluetoothProfile.HEADSET_CLIENT, headsetClientPriority);
-            data.setProfilePriority(BluetoothProfile.HID_HOST, hidHostPriority);
-            data.setProfilePriority(BluetoothProfile.PAN, panPriority);
-            data.setProfilePriority(BluetoothProfile.PBAP, pbapPriority);
-            data.setProfilePriority(BluetoothProfile.PBAP_CLIENT, pbapClientPriority);
-            data.setProfilePriority(BluetoothProfile.MAP, mapPriority);
-            data.setProfilePriority(BluetoothProfile.MAP_CLIENT, mapClientPriority);
-            data.setProfilePriority(BluetoothProfile.SAP, sapPriority);
-            data.setProfilePriority(BluetoothProfile.HEARING_AID, hearingaidPriority);
+            data.setProfileConnectionPolicy(BluetoothProfile.A2DP, a2dpConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.A2DP_SINK, a2dpSinkConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.HEADSET, headsetConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.HEADSET_CLIENT,
+                    headsetClientConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.HID_HOST, hidHostConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.PAN, panConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.PBAP, pbapConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.PBAP_CLIENT,
+                    pbapClientConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.MAP, mapConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.MAP_CLIENT, mapClientConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.SAP, sapConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.HEARING_AID,
+                    hearingaidConnectionPolicy);
             data.a2dpSupportsOptionalCodecs = a2dpSupportsOptionalCodec;
             data.a2dpOptionalCodecsEnabled = a2dpOptionalCodecEnabled;
             mMetadataCache.put(address, data);
@@ -715,6 +906,93 @@
 
     }
 
+    /**
+     * Get the key that retrieves a bluetooth headset's priority.
+     */
+    private static String getLegacyHeadsetPriorityKey(String address) {
+        return LEGACY_HEADSET_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth a2dp sink's priority.
+     */
+    private static String getLegacyA2dpSinkPriorityKey(String address) {
+        return LEGACY_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth a2dp src's priority.
+     */
+    private static String getLegacyA2dpSrcPriorityKey(String address) {
+        return LEGACY_A2DP_SRC_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth a2dp device's ability to support optional codecs.
+     */
+    private static String getLegacyA2dpSupportsOptionalCodecsKey(String address) {
+        return LEGACY_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX
+                + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves whether a bluetooth a2dp device should have optional codecs
+     * enabled.
+     */
+    private static String getLegacyA2dpOptionalCodecsEnabledKey(String address) {
+        return LEGACY_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX
+                + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth Input Device's priority.
+     */
+    private static String getLegacyHidHostPriorityKey(String address) {
+        return LEGACY_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth pan client priority.
+     */
+    private static String getLegacyPanPriorityKey(String address) {
+        return LEGACY_PAN_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth hearing aid priority.
+     */
+    private static String getLegacyHearingAidPriorityKey(String address) {
+        return LEGACY_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth map priority.
+     */
+    private static String getLegacyMapPriorityKey(String address) {
+        return LEGACY_MAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth map client priority.
+     */
+    private static String getLegacyMapClientPriorityKey(String address) {
+        return LEGACY_MAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth pbap client priority.
+     */
+    private static String getLegacyPbapClientPriorityKey(String address) {
+        return LEGACY_PBAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Get the key that retrieves a bluetooth sap priority.
+     */
+    private static String getLegacySapPriorityKey(String address) {
+        return LEGACY_SAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+    }
+
     private void loadDatabase() {
         Log.d(TAG, "Load Database");
         Message message = mHandler.obtainMessage(MSG_LOAD_DATABASE);
@@ -738,7 +1016,8 @@
         mHandler.sendMessage(message);
     }
 
-    private void deleteDatabase(Metadata data) {
+    @VisibleForTesting
+    void deleteDatabase(Metadata data) {
         String address = data.getAddress();
         if (address == null) {
             Log.e(TAG, "deleteDatabase: address is null");
@@ -775,7 +1054,7 @@
                 // Do not log anything if metadata doesn't fall into above categories
                 return;
         }
-        StatsLog.write(StatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
                 mAdapterService.obfuscateAddress(device),
                 BluetoothProtoEnums.DEVICE_INFO_EXTERNAL, callingApp, manufacturerName, modelName,
                 hardwareVersion, softwareVersion);
diff --git a/src/com/android/bluetooth/btservice/storage/Metadata.java b/src/com/android/bluetooth/btservice/storage/Metadata.java
index 9151ede..43f618a 100644
--- a/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -17,6 +17,8 @@
 package com.android.bluetooth.btservice.storage;
 
 import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 
@@ -37,99 +39,110 @@
     public boolean migrated;
 
     @Embedded
-    public ProfilePrioritiesEntity profilePriorities;
+    public ProfilePrioritiesEntity profileConnectionPolicies;
 
     @Embedded
     @NonNull
     public CustomizedMetadataEntity publicMetadata;
 
-    public int a2dpSupportsOptionalCodecs;
-    public int a2dpOptionalCodecsEnabled;
+    public @OptionalCodecsSupportStatus int a2dpSupportsOptionalCodecs;
+    public @OptionalCodecsPreferenceStatus int a2dpOptionalCodecsEnabled;
+
+    public long last_active_time;
+    public boolean is_active_a2dp_device;
 
     Metadata(String address) {
         this.address = address;
         migrated = false;
-        profilePriorities = new ProfilePrioritiesEntity();
+        profileConnectionPolicies = new ProfilePrioritiesEntity();
         publicMetadata = new CustomizedMetadataEntity();
         a2dpSupportsOptionalCodecs = BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
         a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+        last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
+        is_active_a2dp_device = true;
     }
 
     String getAddress() {
         return address;
     }
 
-    void setProfilePriority(int profile, int priority) {
+    void setProfileConnectionPolicy(int profile, int connectionPolicy) {
+        // We no longer support BluetoothProfile.PRIORITY_AUTO_CONNECT and are merging it into
+        // BluetoothProfile.CONNECTION_POLICY_ALLOWED
+        if (connectionPolicy > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connectionPolicy = BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+        }
+
         switch (profile) {
             case BluetoothProfile.A2DP:
-                profilePriorities.a2dp_priority = priority;
+                profileConnectionPolicies.a2dp_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.A2DP_SINK:
-                profilePriorities.a2dp_sink_priority = priority;
+                profileConnectionPolicies.a2dp_sink_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.HEADSET:
-                profilePriorities.hfp_priority = priority;
+                profileConnectionPolicies.hfp_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.HEADSET_CLIENT:
-                profilePriorities.hfp_client_priority = priority;
+                profileConnectionPolicies.hfp_client_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.HID_HOST:
-                profilePriorities.hid_host_priority = priority;
+                profileConnectionPolicies.hid_host_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.PAN:
-                profilePriorities.pan_priority = priority;
+                profileConnectionPolicies.pan_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.PBAP:
-                profilePriorities.pbap_priority = priority;
+                profileConnectionPolicies.pbap_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.PBAP_CLIENT:
-                profilePriorities.pbap_client_priority = priority;
+                profileConnectionPolicies.pbap_client_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.MAP:
-                profilePriorities.map_priority = priority;
+                profileConnectionPolicies.map_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.MAP_CLIENT:
-                profilePriorities.map_client_priority = priority;
+                profileConnectionPolicies.map_client_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.SAP:
-                profilePriorities.sap_priority = priority;
+                profileConnectionPolicies.sap_connection_policy = connectionPolicy;
                 break;
             case BluetoothProfile.HEARING_AID:
-                profilePriorities.hearing_aid_priority = priority;
+                profileConnectionPolicies.hearing_aid_connection_policy = connectionPolicy;
                 break;
             default:
                 throw new IllegalArgumentException("invalid profile " + profile);
         }
     }
 
-    int getProfilePriority(int profile) {
+    int getProfileConnectionPolicy(int profile) {
         switch (profile) {
             case BluetoothProfile.A2DP:
-                return profilePriorities.a2dp_priority;
+                return profileConnectionPolicies.a2dp_connection_policy;
             case BluetoothProfile.A2DP_SINK:
-                return profilePriorities.a2dp_sink_priority;
+                return profileConnectionPolicies.a2dp_sink_connection_policy;
             case BluetoothProfile.HEADSET:
-                return profilePriorities.hfp_priority;
+                return profileConnectionPolicies.hfp_connection_policy;
             case BluetoothProfile.HEADSET_CLIENT:
-                return profilePriorities.hfp_client_priority;
+                return profileConnectionPolicies.hfp_client_connection_policy;
             case BluetoothProfile.HID_HOST:
-                return profilePriorities.hid_host_priority;
+                return profileConnectionPolicies.hid_host_connection_policy;
             case BluetoothProfile.PAN:
-                return profilePriorities.pan_priority;
+                return profileConnectionPolicies.pan_connection_policy;
             case BluetoothProfile.PBAP:
-                return profilePriorities.pbap_priority;
+                return profileConnectionPolicies.pbap_connection_policy;
             case BluetoothProfile.PBAP_CLIENT:
-                return profilePriorities.pbap_client_priority;
+                return profileConnectionPolicies.pbap_client_connection_policy;
             case BluetoothProfile.MAP:
-                return profilePriorities.map_priority;
+                return profileConnectionPolicies.map_connection_policy;
             case BluetoothProfile.MAP_CLIENT:
-                return profilePriorities.map_client_priority;
+                return profileConnectionPolicies.map_client_connection_policy;
             case BluetoothProfile.SAP:
-                return profilePriorities.sap_priority;
+                return profileConnectionPolicies.sap_connection_policy;
             case BluetoothProfile.HEARING_AID:
-                return profilePriorities.hearing_aid_priority;
+                return profileConnectionPolicies.hearing_aid_connection_policy;
         }
-        return BluetoothProfile.PRIORITY_UNDEFINED;
+        return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
     }
 
     void setCustomizedMeta(int key, byte[] value) {
@@ -305,8 +318,8 @@
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append(address)
-            .append(" {profile priority(")
-            .append(profilePriorities)
+            .append(" {profile connection policy(")
+            .append(profileConnectionPolicies)
             .append("), optional codec(support=")
             .append(a2dpSupportsOptionalCodecs)
             .append("|enabled=")
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDao.java b/src/com/android/bluetooth/btservice/storage/MetadataDao.java
index 7c5f440..5efdb24 100644
--- a/src/com/android/bluetooth/btservice/storage/MetadataDao.java
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDao.java
@@ -28,7 +28,7 @@
     /**
      * Load all items in the database
      */
-    @Query("SELECT * FROM metadata")
+    @Query("SELECT * FROM metadata ORDER BY last_active_time DESC")
     List<Metadata> load();
 
     /**
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
index fe56e3a..888f0b0 100644
--- a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
@@ -17,6 +17,8 @@
 package com.android.bluetooth.btservice.storage;
 
 import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
 
 import androidx.room.Database;
 import androidx.room.Room;
@@ -31,13 +33,15 @@
 /**
  * MetadataDatabase is a Room database stores Bluetooth persistence data
  */
-@Database(entities = {Metadata.class}, version = 102)
+@Database(entities = {Metadata.class}, version = 104)
 public abstract class MetadataDatabase extends RoomDatabase {
     /**
-     * The database file name
+     * The metadata database file name
      */
     public static final String DATABASE_NAME = "bluetooth_db";
 
+    static int sCurrentConnectionNumber = 0;
+
     protected abstract MetadataDao mMetadataDao();
 
     /**
@@ -51,6 +55,9 @@
                 MetadataDatabase.class, DATABASE_NAME)
                 .addMigrations(MIGRATION_100_101)
                 .addMigrations(MIGRATION_101_102)
+                .addMigrations(MIGRATION_102_103)
+                .addMigrations(MIGRATION_103_104)
+                .allowMainThreadQueries()
                 .build();
     }
 
@@ -65,11 +72,12 @@
         return Room.databaseBuilder(context,
                 MetadataDatabase.class, DATABASE_NAME)
                 .fallbackToDestructiveMigration()
+                .allowMainThreadQueries()
                 .build();
     }
 
     /**
-     * Insert a {@link Metadata} to database
+     * Insert a {@link Metadata} to metadata table
      *
      * @param metadata the data wish to put into storage
      */
@@ -78,7 +86,7 @@
     }
 
     /**
-     * Load all data from database as a {@link List} of {@link Metadata}
+     * Load all data from metadata table as a {@link List} of {@link Metadata}
      *
      * @return a {@link List} of {@link Metadata}
      */
@@ -87,7 +95,7 @@
     }
 
     /**
-     * Delete one of the {@link Metadata} contains in database
+     * Delete one of the {@link Metadata} contained in the metadata table
      *
      * @param address the address of Metadata to delete
      */
@@ -96,7 +104,7 @@
     }
 
     /**
-     * Clear database.
+     * Clear metadata table.
      */
     public void deleteAll() {
         mMetadataDao().deleteAll();
@@ -172,4 +180,127 @@
             database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`");
         }
     };
+
+    @VisibleForTesting
+    static final Migration MIGRATION_102_103 = new Migration(102, 103) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            try {
+                database.execSQL("CREATE TABLE IF NOT EXISTS `metadata_tmp` ("
+                        + "`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, "
+                        + "`a2dpSupportsOptionalCodecs` INTEGER NOT NULL, "
+                        + "`a2dpOptionalCodecsEnabled` INTEGER NOT NULL, "
+                        + "`a2dp_connection_policy` INTEGER, "
+                        + "`a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, "
+                        + "`hfp_client_connection_policy` INTEGER, "
+                        + "`hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, "
+                        + "`pbap_connection_policy` INTEGER, "
+                        + "`pbap_client_connection_policy` INTEGER, "
+                        + "`map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, "
+                        + "`hearing_aid_connection_policy` INTEGER, "
+                        + "`map_client_connection_policy` INTEGER, `manufacturer_name` BLOB, "
+                        + "`model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, "
+                        + "`companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, "
+                        + "`untethered_left_icon` BLOB, `untethered_right_icon` BLOB, "
+                        + "`untethered_case_icon` BLOB, `untethered_left_battery` BLOB, "
+                        + "`untethered_right_battery` BLOB, `untethered_case_battery` BLOB, "
+                        + "`untethered_left_charging` BLOB, `untethered_right_charging` BLOB, "
+                        + "`untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, "
+                        + "PRIMARY KEY(`address`))");
+
+                database.execSQL("INSERT INTO metadata_tmp ("
+                        + "address, migrated, a2dpSupportsOptionalCodecs, "
+                        + "a2dpOptionalCodecsEnabled, a2dp_connection_policy, "
+                        + "a2dp_sink_connection_policy, hfp_connection_policy,"
+                        + "hfp_client_connection_policy, hid_host_connection_policy,"
+                        + "pan_connection_policy, pbap_connection_policy,"
+                        + "pbap_client_connection_policy, map_connection_policy, "
+                        + "sap_connection_policy, hearing_aid_connection_policy, "
+                        + "map_client_connection_policy, manufacturer_name, model_name, "
+                        + "software_version, hardware_version, companion_app, main_icon, "
+                        + "is_untethered_headset, untethered_left_icon, untethered_right_icon, "
+                        + "untethered_case_icon, untethered_left_battery, "
+                        + "untethered_right_battery, untethered_case_battery, "
+                        + "untethered_left_charging, untethered_right_charging, "
+                        + "untethered_case_charging, enhanced_settings_ui_uri) "
+                        + "SELECT "
+                        + "address, migrated, a2dpSupportsOptionalCodecs, "
+                        + "a2dpOptionalCodecsEnabled, a2dp_priority, a2dp_sink_priority, "
+                        + "hfp_priority, hfp_client_priority, hid_host_priority, pan_priority, "
+                        + "pbap_priority, pbap_client_priority, map_priority, sap_priority, "
+                        + "hearing_aid_priority, map_client_priority, "
+                        + "CAST (manufacturer_name AS BLOB), "
+                        + "CAST (model_name AS BLOB), "
+                        + "CAST (software_version AS BLOB), "
+                        + "CAST (hardware_version AS BLOB), "
+                        + "CAST (companion_app AS BLOB), "
+                        + "CAST (main_icon AS BLOB), "
+                        + "CAST (is_untethered_headset AS BLOB), "
+                        + "CAST (untethered_left_icon AS BLOB), "
+                        + "CAST (untethered_right_icon AS BLOB), "
+                        + "CAST (untethered_case_icon AS BLOB), "
+                        + "CAST (untethered_left_battery AS BLOB), "
+                        + "CAST (untethered_right_battery AS BLOB), "
+                        + "CAST (untethered_case_battery AS BLOB), "
+                        + "CAST (untethered_left_charging AS BLOB), "
+                        + "CAST (untethered_right_charging AS BLOB), "
+                        + "CAST (untethered_case_charging AS BLOB), "
+                        + "CAST (enhanced_settings_ui_uri AS BLOB)"
+                        + "FROM metadata");
+
+                database.execSQL("UPDATE metadata_tmp SET a2dp_connection_policy = 100 "
+                        + "WHERE a2dp_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET a2dp_sink_connection_policy = 100 "
+                        + "WHERE a2dp_sink_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET hfp_connection_policy = 100 "
+                        + "WHERE hfp_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET hfp_client_connection_policy = 100 "
+                        + "WHERE hfp_client_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET hid_host_connection_policy = 100 "
+                        + "WHERE hid_host_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET pan_connection_policy = 100 "
+                        + "WHERE pan_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET pbap_connection_policy = 100 "
+                        + "WHERE pbap_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET pbap_client_connection_policy = 100 "
+                        + "WHERE pbap_client_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET map_connection_policy = 100 "
+                        + "WHERE map_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET sap_connection_policy = 100 "
+                        + "WHERE sap_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET hearing_aid_connection_policy = 100 "
+                        + "WHERE hearing_aid_connection_policy = 1000");
+                database.execSQL("UPDATE metadata_tmp SET map_client_connection_policy = 100 "
+                        + "WHERE map_client_connection_policy = 1000");
+
+                database.execSQL("DROP TABLE `metadata`");
+                database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`");
+            } catch (SQLException ex) {
+                // Check if user has new schema, but is just missing the version update
+                Cursor cursor = database.query("SELECT * FROM metadata");
+                if (cursor == null || cursor.getColumnIndex("a2dp_connection_policy") == -1) {
+                    throw ex;
+                }
+            }
+        }
+    };
+
+    @VisibleForTesting
+    static final Migration MIGRATION_103_104 = new Migration(103, 104) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            try {
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `last_active_time` "
+                        + "INTEGER NOT NULL DEFAULT -1");
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `is_active_a2dp_device` "
+                        + "INTEGER NOT NULL DEFAULT 0");
+            } catch (SQLException ex) {
+                // Check if user has new schema, but is just missing the version update
+                Cursor cursor = database.query("SELECT * FROM metadata");
+                if (cursor == null || cursor.getColumnIndex("last_active_time") == -1) {
+                    throw ex;
+                }
+            }
+        }
+    };
 }
diff --git a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
index d87fbde..480a859 100644
--- a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
+++ b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
@@ -23,48 +23,48 @@
 @Entity
 class ProfilePrioritiesEntity {
     /* Bluetooth profile priorities*/
-    public int a2dp_priority;
-    public int a2dp_sink_priority;
-    public int hfp_priority;
-    public int hfp_client_priority;
-    public int hid_host_priority;
-    public int pan_priority;
-    public int pbap_priority;
-    public int pbap_client_priority;
-    public int map_priority;
-    public int sap_priority;
-    public int hearing_aid_priority;
-    public int map_client_priority;
+    public int a2dp_connection_policy;
+    public int a2dp_sink_connection_policy;
+    public int hfp_connection_policy;
+    public int hfp_client_connection_policy;
+    public int hid_host_connection_policy;
+    public int pan_connection_policy;
+    public int pbap_connection_policy;
+    public int pbap_client_connection_policy;
+    public int map_connection_policy;
+    public int sap_connection_policy;
+    public int hearing_aid_connection_policy;
+    public int map_client_connection_policy;
 
     ProfilePrioritiesEntity() {
-        a2dp_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        a2dp_sink_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        hfp_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        hfp_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        hid_host_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        pan_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        pbap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        pbap_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        map_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        sap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        hearing_aid_priority = BluetoothProfile.PRIORITY_UNDEFINED;
-        map_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+        a2dp_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        a2dp_sink_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        hfp_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        hfp_client_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        hid_host_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        pan_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        pbap_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        pbap_client_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        map_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        sap_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        hearing_aid_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        map_client_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
     }
 
     public String toString() {
         StringBuilder builder = new StringBuilder();
-        builder.append("A2DP=").append(a2dp_priority)
-                .append("|A2DP_SINK=").append(a2dp_sink_priority)
-                .append("|HEADSET=").append(hfp_priority)
-                .append("|HEADSET_CLIENT=").append(hfp_client_priority)
-                .append("|HID_HOST=").append(hid_host_priority)
-                .append("|PAN=").append(pan_priority)
-                .append("|PBAP=").append(pbap_priority)
-                .append("|PBAP_CLIENT=").append(pbap_client_priority)
-                .append("|MAP=").append(map_priority)
-                .append("|MAP_CLIENT=").append(map_client_priority)
-                .append("|SAP=").append(sap_priority)
-                .append("|HEARING_AID=").append(hearing_aid_priority);
+        builder.append("A2DP=").append(a2dp_connection_policy)
+                .append("|A2DP_SINK=").append(a2dp_sink_connection_policy)
+                .append("|HEADSET=").append(hfp_connection_policy)
+                .append("|HEADSET_CLIENT=").append(hfp_client_connection_policy)
+                .append("|HID_HOST=").append(hid_host_connection_policy)
+                .append("|PAN=").append(pan_connection_policy)
+                .append("|PBAP=").append(pbap_connection_policy)
+                .append("|PBAP_CLIENT=").append(pbap_client_connection_policy)
+                .append("|MAP=").append(map_connection_policy)
+                .append("|MAP_CLIENT=").append(map_client_connection_policy)
+                .append("|SAP=").append(sap_connection_policy)
+                .append("|HEARING_AID=").append(hearing_aid_connection_policy);
 
         return builder.toString();
     }
diff --git a/src/com/android/bluetooth/gatt/AppScanStats.java b/src/com/android/bluetooth/gatt/AppScanStats.java
index a895ece..4cab832 100644
--- a/src/com/android/bluetooth/gatt/AppScanStats.java
+++ b/src/com/android/bluetooth/gatt/AppScanStats.java
@@ -22,9 +22,9 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.WorkSource;
-import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.internal.app.IBatteryStats;
 
 import java.text.DateFormat;
@@ -111,7 +111,7 @@
     static final int SCAN_TIMEOUT_MS = 30 * 60 * 1000;
 
     public String appName;
-    public WorkSource mWorkSource; // Used for BatteryStats and StatsLog
+    public WorkSource mWorkSource; // Used for BatteryStats and BluetoothStatsLog
     private int mScansStarted = 0;
     private int mScansStopped = 0;
     public boolean isRegistered = false;
@@ -159,7 +159,8 @@
                 } catch (RemoteException e) {
                     /* ignore */
                 }
-                StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, 100);
+                BluetoothStatsLog.write(
+                        BluetoothStatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, 100);
             }
         }
 
@@ -233,8 +234,8 @@
         } catch (RemoteException e) {
             /* ignore */
         }
-        StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
-                StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
+                BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON,
                 scan.isFilterScan, scan.isBackgroundScan, scan.isOpportunisticScan);
 
         mOngoingScans.put(scannerId, scan);
@@ -297,9 +298,10 @@
         } catch (RemoteException e) {
             /* ignore */
         }
-        StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, scan.results % 100);
-        StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
-                StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF,
+        BluetoothStatsLog.write(
+                BluetoothStatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, scan.results % 100);
+        BluetoothStatsLog.write(BluetoothStatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
+                BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF,
                 scan.isFilterScan, scan.isBackgroundScan, scan.isOpportunisticScan);
     }
 
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index 8d372e7..0b44cde 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -466,22 +467,25 @@
 
         @Override
         public void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
-                List storages, String callingPackage) {
+                List storages, String callingPackage, String callingFeatureId) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.startScan(scannerId, settings, filters, storages, callingPackage);
+            service.startScan(scannerId, settings, filters, storages, callingPackage,
+                    callingFeatureId);
         }
 
         @Override
         public void startScanForIntent(PendingIntent intent, ScanSettings settings,
-                List<ScanFilter> filters, String callingPackage) throws RemoteException {
+                List<ScanFilter> filters, String callingPackage, String callingFeatureId)
+                throws RemoteException {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.registerPiAndStartScan(intent, settings, filters, callingPackage);
+            service.registerPiAndStartScan(intent, settings, filters, callingPackage,
+                    callingFeatureId);
         }
 
         @Override
@@ -1933,7 +1937,8 @@
     }
 
     void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
-            List<List<ResultStorageDescriptor>> storages, String callingPackage) {
+            List<List<ResultStorageDescriptor>> storages, String callingPackage,
+            @Nullable String callingFeatureId) {
         if (DBG) {
             Log.d(TAG, "start scan with filters");
         }
@@ -1947,13 +1952,11 @@
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         scanClient.isQApp = Utils.isQApp(this, callingPackage);
         if (scanClient.isQApp) {
-            scanClient.hasLocationPermission =
-                    Utils.checkCallerHasFineLocation(
-                            this, mAppOps, callingPackage, scanClient.userHandle);
+            scanClient.hasLocationPermission = Utils.checkCallerHasFineLocation(this, mAppOps,
+                    callingPackage, callingFeatureId, scanClient.userHandle);
         } else {
-            scanClient.hasLocationPermission =
-                    Utils.checkCallerHasCoarseOrFineLocation(
-                            this, mAppOps, callingPackage, scanClient.userHandle);
+            scanClient.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(this,
+                    mAppOps, callingPackage, callingFeatureId, scanClient.userHandle);
         }
         scanClient.hasNetworkSettingsPermission =
                 Utils.checkCallerHasNetworkSettingsPermission(this);
@@ -1976,7 +1979,7 @@
     }
 
     void registerPiAndStartScan(PendingIntent pendingIntent, ScanSettings settings,
-            List<ScanFilter> filters, String callingPackage) {
+            List<ScanFilter> filters, String callingPackage, @Nullable String callingFeatureId) {
         if (DBG) {
             Log.d(TAG, "start scan with filters, for PendingIntent");
         }
@@ -2008,10 +2011,10 @@
         try {
             if (app.mIsQApp) {
                 app.hasLocationPermission = Utils.checkCallerHasFineLocation(
-                      this, mAppOps, callingPackage, app.mUserHandle);
+                      this, mAppOps, callingPackage, callingFeatureId, app.mUserHandle);
             } else {
                 app.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
-                      this, mAppOps, callingPackage, app.mUserHandle);
+                      this, mAppOps, callingPackage, callingFeatureId, app.mUserHandle);
             }
         } catch (SecurityException se) {
             // No need to throw here. Just mark as not granted.
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
index b32d304..1db0be6 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
@@ -48,7 +48,7 @@
     private HearingAidNativeInterface() {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         if (mAdapter == null) {
-            Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+            Log.wtf(TAG, "No Bluetooth Adapter Available");
         }
     }
 
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index 3647a65..5d013dd 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -29,15 +29,16 @@
 import android.os.HandlerThread;
 import android.os.ParcelUuid;
 import android.util.Log;
-import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
 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.ServiceFactory;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -235,11 +236,11 @@
             return false;
         }
 
-        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
+        if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return false;
         }
         ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
-        if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.HearingAid)) {
+        if (!ArrayUtils.contains(featureUuids, BluetoothUuid.HEARING_AID)) {
             Log.e(TAG, "Cannot connect to " + device + " : Remote does not have Hearing Aid UUID");
             return false;
         }
@@ -366,19 +367,18 @@
             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);
+        // Check connection policy and accept or reject the connection.
+        int connectionPolicy = getConnectionPolicy(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);
+        } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+                && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            // Otherwise, reject the connection if connectionPolicy is not valid.
+            Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
             return false;
         }
         return true;
@@ -397,7 +397,7 @@
         synchronized (mStateMachines) {
             for (BluetoothDevice device : bondedDevices) {
                 final ParcelUuid[] featureUuids = device.getUuids();
-                if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.HearingAid)) {
+                if (!ArrayUtils.contains(featureUuids, BluetoothUuid.HEARING_AID)) {
                     continue;
                 }
                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
@@ -454,26 +454,51 @@
     }
 
     /**
-     * Set the priority of the Hearing Aid profile.
+     * Set connection policy of the profile and connects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
      *
-     * @param device the remote device
-     * @param priority the priority of the profile
-     * @return true on success, otherwise false
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
      */
-    public boolean setPriority(BluetoothDevice device, int priority) {
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         if (DBG) {
-            Log.d(TAG, "Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
         mAdapterService.getDatabase()
-                .setProfilePriority(device, BluetoothProfile.HEARING_AID, priority);
+                .setProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID, connectionPolicy);
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
         return true;
     }
 
-    public int getPriority(BluetoothDevice device) {
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         return mAdapterService.getDatabase()
-                .getProfilePriority(device, BluetoothProfile.HEARING_AID);
+                .getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID);
     }
 
     void setVolume(int volume) {
@@ -633,8 +658,8 @@
             Log.d(TAG, "reportActiveDevice(" + device + ")");
         }
 
-        StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.HEARING_AID,
-                mAdapterService.obfuscateAddress(device));
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED,
+                BluetoothProfile.HEARING_AID, mAdapterService.obfuscateAddress(device));
 
         Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
@@ -884,21 +909,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             HearingAidService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             HearingAidService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
 
         @Override
@@ -911,17 +936,6 @@
         }
 
         @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) {
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
index 5b8d798..2d41399 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
@@ -54,9 +54,9 @@
 import android.util.Log;
 
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -178,7 +178,7 @@
                         Log.d(TAG, "Disconnected: stack event: " + event);
                     }
                     if (!mDevice.equals(event.device)) {
-                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                        Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
                     }
                     switch (event.type) {
                         case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -282,7 +282,7 @@
                     HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
                     log("Connecting: stack event: " + event);
                     if (!mDevice.equals(event.device)) {
-                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                        Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
                     }
                     switch (event.type) {
                         case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -367,7 +367,7 @@
                     HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
                     log("Disconnecting: stack event: " + event);
                     if (!mDevice.equals(event.device)) {
-                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                        Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
                     }
                     switch (event.type) {
                         case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -460,7 +460,7 @@
                     HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
                     log("Connected: stack event: " + event);
                     if (!mDevice.equals(event.device)) {
-                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                        Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
                     }
                     switch (event.type) {
                         case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
diff --git a/src/com/android/bluetooth/hfp/AtPhonebook.java b/src/com/android/bluetooth/hfp/AtPhonebook.java
index 937e767..d4adfb1 100644
--- a/src/com/android/bluetooth/hfp/AtPhonebook.java
+++ b/src/com/android/bluetooth/hfp/AtPhonebook.java
@@ -31,7 +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 com.android.bluetooth.util.GsmAlphabet;
 
 import java.util.HashMap;
 
diff --git a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
index 61c1163..29c571d 100644
--- a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
@@ -63,7 +63,7 @@
         } 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);
+            Log.wtf(TAG, "FATAL: Stack sent event while service is not available: " + event);
         }
     }
 
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index 941ec14..d1dbe32 100644
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -17,11 +17,8 @@
 package com.android.bluetooth.hfp;
 
 import android.bluetooth.BluetoothDevice;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Looper;
+import android.os.Handler;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -31,11 +28,10 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.TelephonyIntents;
 
 import java.util.HashMap;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 
 /**
@@ -52,14 +48,12 @@
     private final HeadsetService mHeadsetService;
     private final TelephonyManager mTelephonyManager;
     private final SubscriptionManager mSubscriptionManager;
+    private final Handler mHandler;
 
     private ServiceState mServiceState;
 
     // 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;
     // Number of active (foreground) calls
     private int mNumActive;
     // Current Call Setup State
@@ -88,9 +82,10 @@
         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);
+        mHandler = new Handler(headsetService.getStateMachinesThreadLooper());
+        mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener();
+        mSubscriptionManager.addOnSubscriptionsChangedListener(command -> mHandler.post(command),
+                mOnSubscriptionsChangedListener);
     }
 
     /**
@@ -160,14 +155,8 @@
             return;
         }
         Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
-        mPhoneStateListener = new HeadsetPhoneStateListener(
-                mHeadsetService.getStateMachinesThreadLooper());
+        mPhoneStateListener = new HeadsetPhoneStateListener(command -> mHandler.post(command));
         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() {
@@ -178,9 +167,6 @@
         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;
     }
 
@@ -215,6 +201,10 @@
         mNumHeld = numHeldCall;
     }
 
+    ServiceState getServiceState() {
+        return mServiceState;
+    }
+
     int getCindSignal() {
         return mCindSignal;
     }
@@ -244,29 +234,34 @@
         return (mNumActive >= 1);
     }
 
-    private void sendDeviceStateChanged() {
-        int service =
-                mIsSimStateLoaded ? mCindService : HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
+    private synchronized void sendDeviceStateChanged() {
         // When out of service, send signal strength as 0. Some devices don't
         // use the service indicator, but only the signal indicator
-        int signal = service == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mCindSignal : 0;
+        int signal = mCindService == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mCindSignal : 0;
 
-        Log.d(TAG, "sendDeviceStateChanged. mService=" + mCindService + " mIsSimStateLoaded="
-                + mIsSimStateLoaded + " mSignal=" + signal + " mRoam=" + mCindRoam
+        Log.d(TAG, "sendDeviceStateChanged. mService=" + mCindService
+                + " mSignal=" + mCindSignal + " mRoam=" + mCindRoam
                 + " mBatteryCharge=" + mCindBatteryCharge);
         mHeadsetService.onDeviceStateChanged(
-                new HeadsetDeviceState(service, mCindRoam, signal, mCindBatteryCharge));
+                new HeadsetDeviceState(mCindService, mCindRoam, signal, mCindBatteryCharge));
     }
 
     private class HeadsetPhoneStateOnSubscriptionChangedListener
             extends OnSubscriptionsChangedListener {
-        HeadsetPhoneStateOnSubscriptionChangedListener(Looper looper) {
-            super(looper);
+        HeadsetPhoneStateOnSubscriptionChangedListener() {
+            super();
         }
 
         @Override
         public void onSubscriptionsChanged() {
             synchronized (mDeviceEventMap) {
+                int simState = mTelephonyManager.getSimState();
+                if (simState != TelephonyManager.SIM_STATE_READY) {
+                    mServiceState = null;
+                    mCindSignal = 0;
+                    mCindService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
+                    sendDeviceStateChanged();
+                }
                 stopListenForPhoneState();
                 startListenForPhoneState();
             }
@@ -274,8 +269,8 @@
     }
 
     private class HeadsetPhoneStateListener extends PhoneStateListener {
-        HeadsetPhoneStateListener(Looper looper) {
-            super(looper);
+        HeadsetPhoneStateListener(Executor executor) {
+            super(executor);
         }
 
         @Override
@@ -293,32 +288,7 @@
             }
             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);
-
+            sendDeviceStateChanged();
         }
 
         @Override
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index 8f9adae..d60085a 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -31,19 +31,17 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.BatteryManager;
+import android.os.Handler;
 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.telecom.PhoneAccount;
 import android.util.Log;
-import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
@@ -89,7 +87,7 @@
     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 ParcelUuid[] HEADSET_UUIDS = {BluetoothUuid.HSP, BluetoothUuid.HFP};
     private static final int[] CONNECTING_CONNECTED_STATES =
             {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED};
     private static final int DIALING_OUT_TIMEOUT_MS = 10000;
@@ -98,6 +96,7 @@
     private BluetoothDevice mActiveDevice;
     private AdapterService mAdapterService;
     private HandlerThread mStateMachinesThread;
+    private Handler mStateMachinesThreadHandler;
     // This is also used as a lock for shared data in HeadsetService
     private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>();
     private HeadsetNativeInterface mNativeInterface;
@@ -195,11 +194,12 @@
             mVoiceRecognitionStarted = false;
             mVirtualCallStarted = false;
             if (mDialingOutTimeoutEvent != null) {
-                mStateMachinesThread.getThreadHandler().removeCallbacks(mDialingOutTimeoutEvent);
+                getStateMachinesThreadHandler()
+                        .removeCallbacks(mDialingOutTimeoutEvent);
                 mDialingOutTimeoutEvent = null;
             }
             if (mVoiceRecognitionTimeoutEvent != null) {
-                mStateMachinesThread.getThreadHandler()
+                getStateMachinesThreadHandler()
                         .removeCallbacks(mVoiceRecognitionTimeoutEvent);
                 mVoiceRecognitionTimeoutEvent = null;
                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
@@ -219,6 +219,7 @@
         // Step 2: Stop handler thread
         mStateMachinesThread.quitSafely();
         mStateMachinesThread = null;
+        mStateMachinesThreadHandler = null;
         // Step 1: Clear
         synchronized (mStateMachines) {
             mAdapterService = null;
@@ -359,7 +360,7 @@
                         synchronized (mStateMachines) {
                             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
                             if (stateMachine == null) {
-                                Log.wtfStack(TAG, "Cannot find state machine for " + device);
+                                Log.wtf(TAG, "Cannot find state machine for " + device);
                                 return;
                             }
                             stateMachine.sendMessage(
@@ -476,21 +477,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             HeadsetService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             HeadsetService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
 
         @Override
@@ -680,8 +681,9 @@
 
     public boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
-            Log.w(TAG, "connect: PRIORITY_OFF, device=" + device + ", " + Utils.getUidPidString());
+        if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            Log.w(TAG, "connect: CONNECTION_POLICY_FORBIDDEN, device=" + device + ", "
+                    + Utils.getUidPidString());
             return false;
         }
         ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
@@ -815,19 +817,51 @@
         }
     }
 
-    public boolean setPriority(BluetoothDevice device, int priority) {
+    /**
+     * Set connection policy of the profile and connects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        Log.i(TAG, "setPriority: device=" + device + ", priority=" + priority + ", "
-                + Utils.getUidPidString());
+        Log.i(TAG, "setConnectionPolicy: device=" + device
+                + ", connectionPolicy=" + connectionPolicy + ", " + Utils.getUidPidString());
         mAdapterService.getDatabase()
-                .setProfilePriority(device, BluetoothProfile.HEADSET, priority);
+                .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, connectionPolicy);
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
         return true;
     }
 
-    public int getPriority(BluetoothDevice device) {
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAdapterService.getDatabase()
-                .getProfilePriority(device, BluetoothProfile.HEADSET);
+                .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET);
     }
 
     boolean startVoiceRecognition(BluetoothDevice device) {
@@ -871,8 +905,7 @@
                             + ", fall back to requesting device");
                     device = mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice;
                 }
-                mStateMachinesThread.getThreadHandler()
-                        .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+                getStateMachinesThreadHandler().removeCallbacks(mVoiceRecognitionTimeoutEvent);
                 mVoiceRecognitionTimeoutEvent = null;
                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                     mSystemInterface.getVoiceRecognitionWakeLock().release();
@@ -1339,7 +1372,7 @@
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startActivity(intent);
             mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(fromDevice);
-            mStateMachinesThread.getThreadHandler()
+            getStateMachinesThreadHandler()
                     .postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
             return true;
         }
@@ -1424,28 +1457,14 @@
                         + " 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()
+            getStateMachinesThreadHandler()
                     .postDelayed(mVoiceRecognitionTimeoutEvent, sStartVrTimeoutMs);
+
             if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                 mSystemInterface.getVoiceRecognitionWakeLock().acquire(sStartVrTimeoutMs);
             }
@@ -1470,8 +1489,9 @@
                 if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                     mSystemInterface.getVoiceRecognitionWakeLock().release();
                 }
-                mStateMachinesThread.getThreadHandler()
+                getStateMachinesThreadHandler()
                         .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+
                 mVoiceRecognitionTimeoutEvent = null;
             }
             if (mVoiceRecognitionStarted) {
@@ -1507,7 +1527,7 @@
             if (mDialingOutTimeoutEvent != null) {
                 // Send result to state machine when dialing starts
                 if (callState == HeadsetHalConstants.CALL_STATE_DIALING) {
-                    mStateMachinesThread.getThreadHandler()
+                    getStateMachinesThreadHandler()
                             .removeCallbacks(mDialingOutTimeoutEvent);
                     doForStateMachine(mDialingOutTimeoutEvent.mDialingOutDevice,
                             stateMachine -> stateMachine.sendMessage(
@@ -1516,14 +1536,14 @@
                 } 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()
+                    if (!getStateMachinesThreadHandler()
                             .hasCallbacks(mDialingOutTimeoutEvent)) {
                         mDialingOutTimeoutEvent = null;
                     }
                 }
             }
         }
-        mStateMachinesThread.getThreadHandler().post(() -> {
+        getStateMachinesThreadHandler().post(() -> {
             boolean isCallIdleBefore = mSystemInterface.isCallIdle();
             mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
             mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
@@ -1537,7 +1557,7 @@
         doForEachConnectedStateMachine(
                 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
                         new HeadsetCallState(numActive, numHeld, callState, number, type, name)));
-        mStateMachinesThread.getThreadHandler().post(() -> {
+        getStateMachinesThreadHandler().post(() -> {
             if (callState == HeadsetHalConstants.CALL_STATE_IDLE
                     && mSystemInterface.isCallIdle() && !isAudioOn()) {
                 // Resume A2DP when call ended and SCO is not connected
@@ -1706,8 +1726,8 @@
 
     private void broadcastActiveDevice(BluetoothDevice device) {
         logD("broadcastActiveDevice: " + device);
-        StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.HEADSET,
-                mAdapterService.obfuscateAddress(device));
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED,
+                BluetoothProfile.HEADSET, mAdapterService.obfuscateAddress(device));
         Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -1727,19 +1747,18 @@
             Log.w(TAG, "okToAcceptConnection: return false as quiet mode enabled");
             return false;
         }
-        // Check priority and accept or reject the connection.
-        int priority = getPriority(device);
+        // Check connection policy and accept or reject the connection.
+        int connectionPolicy = getConnectionPolicy(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);
+        } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+                && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            // Otherwise, reject the connection if connection policy is not valid.
+            Log.w(TAG, "okToAcceptConnection: return false, connectionPolicy=" + connectionPolicy);
             return false;
         }
         List<BluetoothDevice> connectingConnectedDevices =
@@ -1850,4 +1869,11 @@
             Log.d(TAG, message);
         }
     }
+
+    private Handler getStateMachinesThreadHandler() {
+        if (mStateMachinesThreadHandler == null) {
+            mStateMachinesThreadHandler = new Handler(mStateMachinesThread.getLooper());
+        }
+        return mStateMachinesThreadHandler;
+    }
 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 06c4822..8656383 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -31,15 +31,17 @@
 import android.os.UserHandle;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.StatsLog;
 
+import com.android.bluetooth.BluetoothStatsLog;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -306,7 +308,7 @@
         // Should not be called from enter() method
         void broadcastAudioState(BluetoothDevice device, int fromState, int toState) {
             stateLogD("broadcastAudioState: " + device + ": " + fromState + "->" + toState);
-            StatsLog.write(StatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
+            BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
                     mAdapterService.obfuscateAddress(device),
                     getConnectionStateFromAudioState(toState),
                     TextUtils.equals(mAudioParams.get(HEADSET_WBS), HEADSET_AUDIO_FEATURE_ON)
@@ -392,8 +394,8 @@
             logi(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
         }
 
-        void stateLogWtfStack(String msg) {
-            Log.wtfStack(TAG, getName() + ": " + msg);
+        void stateLogWtf(String msg) {
+            Log.wtf(TAG, getName() + ": " + msg);
         }
 
         /**
@@ -518,8 +520,9 @@
                         stateLogI("accept incoming connection");
                         transitionTo(mConnecting);
                     } else {
-                        stateLogI("rejected incoming HF, priority=" + mHeadsetService.getPriority(
-                                mDevice) + " bondState=" + mAdapterService.getBondState(mDevice));
+                        stateLogI("rejected incoming HF, connectionPolicy="
+                                + mHeadsetService.getConnectionPolicy(mDevice) + " bondState="
+                                + mAdapterService.getBondState(mDevice));
                         // Reject the connection and stay in Disconnected state itself
                         if (!mNativeInterface.disconnectHfp(mDevice)) {
                             stateLogE("failed to disconnect");
@@ -841,8 +844,6 @@
                     break;
                 }
                 case CALL_STATE_CHANGED: {
-                    if (mDeviceSilenced) break;
-
                     HeadsetCallState callState = (HeadsetCallState) message.obj;
                     if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
                         stateLogW("processCallState: failed to update call state " + callState);
@@ -1587,7 +1588,7 @@
             if (number.charAt(number.length() - 1) == ';') {
                 number = number.substring(0, number.length() - 1);
             }
-            dialNumber = PhoneNumberUtils.convertPreDial(number);
+            dialNumber = Utils.convertPreDial(number);
         }
         if (!mHeadsetService.dialOutgoingCall(mDevice, dialNumber)) {
             Log.w(TAG, "processDialCall, failed to dial in service");
@@ -1703,7 +1704,16 @@
     }
 
     private void processAtCops(BluetoothDevice device) {
-        String operatorName = mSystemInterface.getNetworkOperator();
+        // Get operator name suggested by Telephony
+        String operatorName = null;
+        ServiceState serviceState = mSystemInterface.getHeadsetPhoneState().getServiceState();
+        if (serviceState != null) {
+            operatorName = serviceState.getOperatorAlpha();
+        }
+        if (mSystemInterface.isInCall() || operatorName == null || operatorName.equals("")) {
+            // Get operator name suggested by Telecom
+            operatorName = mSystemInterface.getNetworkOperator();
+        }
         if (operatorName == null) {
             operatorName = "";
         }
@@ -1868,7 +1878,7 @@
         String vendorId = deviceInfo[0];
         String productId = deviceInfo[1];
         String version = deviceInfo[2];
-        StatsLog.write(StatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
                 mAdapterService.obfuscateAddress(device), BluetoothProtoEnums.DEVICE_INFO_INTERNAL,
                 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL, vendorId, productId, version,
                 null);
@@ -2022,7 +2032,7 @@
             events |= PhoneStateListener.LISTEN_SERVICE_STATE;
         }
         if (mAgIndicatorEnableState != null && mAgIndicatorEnableState.signal) {
-            events |= PhoneStateListener.LISTEN_SIGNAL_STRENGTHS;
+            events |= PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH;
         }
         mSystemInterface.getHeadsetPhoneState().listenForPhoneState(mDevice, events);
     }
diff --git a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
index 81090eb..52ca1bc 100644
--- a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.hfp;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.IBluetoothHeadsetPhone;
@@ -24,6 +26,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.media.AudioManager;
 import android.os.IBinder;
 import android.os.PowerManager;
@@ -32,6 +37,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.List;
+
 /**
  * Defines system calls that is used by state machine/service to either send or receive
  * messages from the Android System.
@@ -70,7 +77,7 @@
 
     HeadsetSystemInterface(HeadsetService headsetService) {
         if (headsetService == null) {
-            Log.wtfStack(TAG, "HeadsetService parameter is null");
+            Log.wtf(TAG, "HeadsetService parameter is null");
         }
         mHeadsetService = headsetService;
         mAudioManager = (AudioManager) mHeadsetService.getSystemService(Context.AUDIO_SERVICE);
@@ -88,15 +95,48 @@
     public synchronized void init() {
         // Bind to Telecom phone proxy service
         Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());
-        intent.setComponent(intent.resolveSystemService(mHeadsetService.getPackageManager(), 0));
+        intent.setComponent(resolveSystemService(mHeadsetService.getPackageManager(), 0, intent));
         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);
+            Log.wtf(TAG, "Could not bind to IBluetoothHeadsetPhone Service, intent=" + intent);
         }
     }
 
     /**
+     * Special function for use by the system to resolve service
+     * intents to system apps.  Throws an exception if there are
+     * multiple potential matches to the Intent.  Returns null if
+     * there are no matches.
+     */
+    private @Nullable ComponentName resolveSystemService(@NonNull PackageManager pm,
+            @PackageManager.ComponentInfoFlags int flags, Intent intent) {
+        if (intent.getComponent() != null) {
+            return intent.getComponent();
+        }
+
+        List<ResolveInfo> results = pm.queryIntentServices(intent, flags);
+        if (results == null) {
+            return null;
+        }
+        ComponentName comp = null;
+        for (int i = 0; i < results.size(); i++) {
+            ResolveInfo ri = results.get(i);
+            if ((ri.serviceInfo.applicationInfo.flags& ApplicationInfo.FLAG_SYSTEM) == 0) {
+                continue;
+            }
+            ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
+                    ri.serviceInfo.name);
+            if (comp != null) {
+                throw new IllegalStateException("Multiple system services handle " + this
+                        + ": " + comp + ", " + foundComp);
+            }
+            comp = foundComp;
+        }
+        return comp;
+    }
+
+    /**
      * Stop this system interface
      */
     public synchronized void stop() {
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index e7412fe..527a29e 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -257,21 +257,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             HeadsetClientService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             HeadsetClientService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
 
         @Override
@@ -482,8 +482,9 @@
             return false;
         }
 
-        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
-            Log.w(TAG, "Connection not allowed: <" + device.getAddress() + "> is PRIORITY_OFF");
+        if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            Log.w(TAG, "Connection not allowed: <" + device.getAddress()
+                    + "> is CONNECTION_POLICY_FORBIDDEN");
             return false;
         }
 
@@ -551,20 +552,52 @@
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
-    public boolean setPriority(BluetoothDevice device, int priority) {
+    /**
+     * Set connection policy of the profile and connects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         if (DBG) {
-            Log.d(TAG, "Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        AdapterService.getAdapterService().getDatabase()
-                .setProfilePriority(device, BluetoothProfile.HEADSET_CLIENT, priority);
+        AdapterService.getAdapterService().getDatabase().setProfileConnectionPolicy(
+                device, BluetoothProfile.HEADSET_CLIENT, connectionPolicy);
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
         return true;
     }
 
-    public int getPriority(BluetoothDevice device) {
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         return AdapterService.getAdapterService().getDatabase()
-                .getProfilePriority(device, BluetoothProfile.HEADSET_CLIENT);
+                .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET_CLIENT);
     }
 
     boolean startVoiceRecognition(BluetoothDevice device) {
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index 3093aaf..3b241e3 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -51,18 +51,19 @@
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Pair;
-import android.util.StatsLog;
 
 import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
 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.statemachine.IState;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IState;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
+import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -876,8 +877,9 @@
                         mCurrentDevice = device;
                         transitionTo(mConnecting);
                     } else {
-                        Log.i(TAG, "Incoming AG rejected. priority=" + mService.getPriority(device)
-                                + " bondState=" + device.getBondState());
+                        Log.i(TAG, "Incoming AG rejected. connectionPolicy="
+                                + mService.getConnectionPolicy(device) + " bondState="
+                                + device.getBondState());
                         // reject the connection and stay in Disconnected state
                         // itself
                         mNativeInterface.disconnect(getByteAddress(device));
@@ -1664,7 +1666,7 @@
     }
 
     private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
-        StatsLog.write(StatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
+        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
                 AdapterService.getAdapterService().obfuscateAddress(device),
                 getConnectionStateFromAudioState(newState), mAudioWbs
                         ? BluetoothHfpProtoEnums.SCO_CODEC_MSBC
@@ -1752,7 +1754,7 @@
         synchronized (this) {
             for (BluetoothDevice device : bondedDevices) {
                 ParcelUuid[] featureUuids = device.getUuids();
-                if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.Handsfree_AG)) {
+                if (!ArrayUtils.contains(featureUuids, BluetoothUuid.HFP_AG)) {
                     continue;
                 }
                 connectionState = getConnectionState(device);
@@ -1767,16 +1769,16 @@
     }
 
     boolean okToConnect(BluetoothDevice device) {
-        int priority = mService.getPriority(device);
+        int connectionPolicy = mService.getConnectionPolicy(device);
         boolean ret = false;
-        // check priority and accept or reject the connection. if priority is
+        // check connection policy and accept or reject the connection. if connection policy 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 ((BluetoothProfile.CONNECTION_POLICY_FORBIDDEN < connectionPolicy) || (
+                (BluetoothProfile.CONNECTION_POLICY_UNKNOWN == connectionPolicy)
+                        && (device.getBondState() != BluetoothDevice.BOND_NONE))) {
             ret = true;
         }
         return ret;
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
index e3c024a..aba50c0 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
@@ -224,7 +224,7 @@
         if (DBG) {
             Log.d(mTAG, "prevConn " + prevConn.isClosing() + " new call " + newCall.getState());
         }
-        if (prevConn.isClosing()
+        if (prevConn.isClosing() && prevConn.getCall().getState() != newCall.getState()
                 && newCall.getState() != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
             return true;
         }
diff --git a/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
index db72a59..e99e448 100644
--- a/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
+++ b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
@@ -49,7 +49,7 @@
     private HidDeviceNativeInterface() {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         if (mAdapter == null) {
-            Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+            Log.wtf(TAG, "No Bluetooth Adapter Available");
         }
     }
 
@@ -180,7 +180,7 @@
         if (service != null) {
             service.onApplicationStateChangedFromNative(getDevice(address), registered);
         } else {
-            Log.wtfStack(TAG, "FATAL: onApplicationStateChanged() "
+            Log.wtf(TAG, "FATAL: onApplicationStateChanged() "
                     + "is called from the stack while service is not available.");
         }
     }
@@ -190,7 +190,7 @@
         if (service != null) {
             service.onConnectStateChangedFromNative(getDevice(address), state);
         } else {
-            Log.wtfStack(TAG, "FATAL: onConnectStateChanged() "
+            Log.wtf(TAG, "FATAL: onConnectStateChanged() "
                     + "is called from the stack while service is not available.");
         }
     }
@@ -200,7 +200,7 @@
         if (service != null) {
             service.onGetReportFromNative(type, id, bufferSize);
         } else {
-            Log.wtfStack(TAG, "FATAL: onGetReport() "
+            Log.wtf(TAG, "FATAL: onGetReport() "
                     + "is called from the stack while service is not available.");
         }
     }
@@ -210,7 +210,7 @@
         if (service != null) {
             service.onSetReportFromNative(reportType, reportId, data);
         } else {
-            Log.wtfStack(TAG, "FATAL: onSetReport() "
+            Log.wtf(TAG, "FATAL: onSetReport() "
                     + "is called from the stack while service is not available.");
         }
     }
@@ -220,7 +220,7 @@
         if (service != null) {
             service.onSetProtocolFromNative(protocol);
         } else {
-            Log.wtfStack(TAG, "FATAL: onSetProtocol() "
+            Log.wtf(TAG, "FATAL: onSetProtocol() "
                     + "is called from the stack while service is not available.");
         }
     }
@@ -230,7 +230,7 @@
         if (service != null) {
             service.onInterruptDataFromNative(reportId, data);
         } else {
-            Log.wtfStack(TAG, "FATAL: onInterruptData() "
+            Log.wtf(TAG, "FATAL: onInterruptData() "
                     + "is called from the stack while service is not available.");
         }
     }
@@ -240,7 +240,7 @@
         if (service != null) {
             service.onVirtualCableUnplugFromNative();
         } else {
-            Log.wtfStack(TAG, "FATAL: onVirtualCableUnplug() "
+            Log.wtf(TAG, "FATAL: onVirtualCableUnplug() "
                     + "is called from the stack while service is not available.");
         }
     }
diff --git a/src/com/android/bluetooth/hid/HidDeviceService.java b/src/com/android/bluetooth/hid/HidDeviceService.java
index b2d7e41..d0a1970 100644
--- a/src/com/android/bluetooth/hid/HidDeviceService.java
+++ b/src/com/android/bluetooth/hid/HidDeviceService.java
@@ -405,6 +405,21 @@
         }
 
         @Override
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+            if (DBG) {
+                Log.d(TAG, "setConnectionPolicy(): device=" + device + " connectionPolicy="
+                        + connectionPolicy);
+            }
+
+            HidDeviceService service = getService();
+            if (service == null) {
+                return false;
+            }
+
+            return service.setConnectionPolicy(device, connectionPolicy);
+        }
+
+        @Override
         public boolean reportError(BluetoothDevice device, byte error) {
             if (DBG) {
                 Log.d(TAG, "reportError(): device=" + device + " error=" + error);
@@ -627,6 +642,35 @@
         return checkDevice(device) && mHidDeviceNativeInterface.disconnect();
     }
 
+    /**
+     * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}
+     * and disconnects Hid device if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy determines whether hid device should be connected or disconnected
+     * @return true if hid device is connected or disconnected, false otherwise
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+        enforceCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
+        if (DBG) {
+            Log.d(TAG, "setConnectionPolicy: device " + device
+                    + " and connectionPolicy " + connectionPolicy);
+        }
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+            return true;
+        }
+        return false;
+    }
+
     synchronized boolean reportError(BluetoothDevice device, byte error) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
diff --git a/src/com/android/bluetooth/hid/HidHostService.java b/src/com/android/bluetooth/hid/HidHostService.java
index 2e0019c..fa1ef59 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -373,21 +373,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             HidHostService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             HidHostService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
 
         /* The following APIs regarding test app for compliance */
@@ -482,8 +482,8 @@
             Log.e(TAG, "Hid Device not disconnected: " + device);
             return false;
         }
-        if (getPriority(device) == BluetoothHidHost.PRIORITY_OFF) {
-            Log.e(TAG, "Hid Device PRIORITY_OFF: " + device);
+        if (getConnectionPolicy(device) == BluetoothHidHost.CONNECTION_POLICY_FORBIDDEN) {
+            Log.e(TAG, "Hid Device CONNECTION_POLICY_FORBIDDEN: " + device);
             return false;
         }
 
@@ -531,26 +531,58 @@
         return inputDevices;
     }
 
-    public boolean setPriority(BluetoothDevice device, int priority) {
+    /**
+     * Set connection policy of the profile and connects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         if (DBG) {
-            Log.d(TAG, "setPriority: " + device.getAddress());
+            Log.d(TAG, "setConnectionPolicy: " + device.getAddress());
         }
         if (DBG) {
-            Log.d(TAG, "Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
         AdapterService.getAdapterService().getDatabase()
-                .setProfilePriority(device, BluetoothProfile.HID_HOST, priority);
+                .setProfileConnectionPolicy(device, BluetoothProfile.HID_HOST, connectionPolicy);
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
         return true;
     }
 
-    public int getPriority(BluetoothDevice device) {
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         if (DBG) {
-            Log.d(TAG, "getPriority: " + device.getAddress());
+            Log.d(TAG, "getConnectionPolicy: " + device.getAddress());
         }
         return AdapterService.getAdapterService().getDatabase()
-                .getProfilePriority(device, BluetoothProfile.HID_HOST);
+                .getProfileConnectionPolicy(device, BluetoothProfile.HID_HOST);
     }
 
     /* The following APIs regarding test app for compliance */
@@ -825,19 +857,18 @@
             Log.w(TAG, "okToConnect: return false as quiet mode enabled");
             return false;
         }
-        // Check priority and accept or reject the connection.
-        int priority = getPriority(device);
+        // Check connection policy and accept or reject the connection.
+        int connectionPolicy = getConnectionPolicy(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);
+        } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+                && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            // Otherwise, reject the connection if connectionPolicy is not valid.
+            Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
             return false;
         }
         return true;
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
index c1c2846..e48b5e2 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppParams.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
@@ -63,16 +63,16 @@
     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 CHAT_STATE = 0x21;
+    private static final int FILTER_CONVO_ID = 0x22;
+    private static final int CONVO_LISTING_SIZE = 0x36;
+    private static final int FILTER_PRESENCE = 0x37;
+    private static final int FILTER_UID_PRESENT = 0x38;
+    private static final int CHAT_STATE_CONVO_ID = 0x39;
+    private static final int FOLDER_VER_COUNTER = 0x23;
+    private static final int FILTER_MESSAGE_HANDLE = 0x24;
+    private static final int NOTIFICATION_FILTER = 0x25;
+    private static final int CONVO_PARAMETER_MASK = 0x26;
 
     // Length defined for Application Parameters
     private static final int MAX_LIST_COUNT_LEN = 0x02; //, 0x0000, 0xFFFF),
@@ -104,8 +104,8 @@
     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 FOLDER_VER_COUNTER_LEN = 0x20;
+    private static final int FILTER_MESSAGE_HANDLE_LEN = 0x08;
     private static final int NOTIFICATION_FILTER_LEN = 0x04;
     private static final int CONVO_PARAMETER_MASK_LEN = 0x04;
 
@@ -1046,7 +1046,7 @@
     public void setFilterMsgHandle(String handle) {
         try {
             mFilterMsgHandle = BluetoothMapUtils.getLongFromString(handle);
-        } catch (UnsupportedEncodingException e) {
+        } catch (UnsupportedEncodingException | NumberFormatException e) {
             Log.w(TAG, "Error creating long from handle string", e);
         }
     }
@@ -1100,7 +1100,7 @@
     public void setFilterConvoId(String id) {
         try {
             mFilterConvoId = SignedLongLong.fromString(id);
-        } catch (UnsupportedEncodingException e) {
+        } catch (UnsupportedEncodingException | NumberFormatException e) {
             Log.w(TAG, "Error creating long from id string", e);
         }
     }
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index 2cda124..09894e8 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -86,13 +86,13 @@
     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_DELIVERY_STATUS = 0x00010000;
+    private static final int MASK_CONVERSATION_ID = 0x00020000;
+    private static final int MASK_CONVERSATION_NAME = 0x00040000;
     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 = 0x00100000;
 
     private static final int CONVO_PARAM_MASK_CONVO_NAME = 0x00000001;
     private static final int CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY = 0x00000002;
@@ -2190,7 +2190,6 @@
         if (tm != null) {
             fi.mPhoneType = tm.getPhoneType();
             fi.mPhoneNum = tm.getLine1Number();
-            fi.mPhoneAlphaTag = tm.getLine1AlphaTag();
         }
     }
 
@@ -2454,7 +2453,6 @@
                         setDeliveryStatus(ele, tmpCursor, fi, ap);
                         setThreadId(ele, tmpCursor, fi, ap);
                         setThreadName(ele, tmpCursor, fi, ap);
-                        setFolderType(ele, tmpCursor, fi, ap);
                     }
                 }
             }
@@ -3709,9 +3707,10 @@
                 if (charset == MAP_MESSAGE_CHARSET_NATIVE) {
                     if (type == 1) { //Inbox
                         message.setSmsBodyPdus(
-                                BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time));
+                                BluetoothMapSmsPdu.getDeliverPdus(mContext, msgBody, phone, time));
                     } else {
-                        message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
+                        message.setSmsBodyPdus(
+                                BluetoothMapSmsPdu.getSubmitPdus(mContext, msgBody, phone));
                     }
                 } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
                     message.setSmsBody(msgBody);
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 8a73ef2..521b481 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -47,12 +47,14 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
@@ -113,7 +115,7 @@
     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_MESSAGE_REMOVED = 1L << 14;
 
     // 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
@@ -313,7 +315,7 @@
 
     public void setObserverRemoteFeatureMask(int remoteSupportedFeatures) {
         mMapSupportedFeatures =
-                remoteSupportedFeatures & BluetoothMapMasInstance.SDP_MAP_MAS_FEATURES;
+                remoteSupportedFeatures & BluetoothMapMasInstance.getFeatureMask();
         if ((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT & mMapSupportedFeatures)
                 != 0) {
             mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
@@ -1452,10 +1454,7 @@
                                                     Context.TELEPHONY_SERVICE);
                                     if (tm != null) {
                                         phone = tm.getLine1Number();
-                                        name = tm.getLine1AlphaTag();
-                                        if (name == null || name.isEmpty()) {
-                                            name = phone;
-                                        }
+                                        name = phone;
                                     }
                                 }
                                 String priority = "no"; // no priority for sms
@@ -1527,15 +1526,21 @@
                         }
                     } while (c.moveToNext());
                 }
+            } catch (IllegalStateException e) {
+                Log.w(TAG, e);
             } finally {
                 if (c != null) {
                     c.close();
                 }
             }
-
+            String eventType = EVENT_TYPE_DELETE;
             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,
+                if (mMapEventReportVersion >= BluetoothMapUtils.MAP_EVENT_REPORT_V12) {
+                    eventType = EVENT_TYPE_REMOVED;
+                    if (V) Log.v(TAG," sent EVENT_TYPE_REMOVED");
+                }
+                Event evt = new Event(eventType, msg.id, getSmsFolderName(msg.type), null,
                         mSmsType);
                 sendEvent(evt);
                 listChanged = true;
@@ -1692,6 +1697,8 @@
                     } while (c.moveToNext());
 
                 }
+            } catch (IllegalStateException e) {
+                Log.w(TAG, e);
             } finally {
                 if (c != null) {
                     c.close();
@@ -2544,6 +2551,37 @@
     private Map<Long, PushMsgInfo> mPushMsgList =
             Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>());
 
+    /**
+     * Add an SMS to the given URI.
+     *
+     * @param resolver the content resolver to use
+     * @param uri the URI to add the message to
+     * @param address the address of the sender
+     * @param body the body of the message
+     * @param subject the pseudo-subject of the message
+     * @param date the timestamp for the message
+     * @return the URI for the new message
+     */
+    private static Uri addMessageToUri(ContentResolver resolver, Uri uri,
+                                      String address, String body, String subject,
+                                      Long date) {
+        ContentValues values = new ContentValues(7);
+        final int statusPending = 32;
+        final int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+        Log.v(TAG, "Telephony addMessageToUri sub id: " + subId);
+
+        values.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
+        values.put(Telephony.Sms.ADDRESS, address);
+        if (date != null) {
+            values.put(Telephony.Sms.DATE, date);
+        }
+        values.put(Telephony.Sms.READ, 0);
+        values.put(Telephony.Sms.SUBJECT, subject);
+        values.put(Telephony.Sms.BODY, body);
+        values.put(Telephony.Sms.STATUS, statusPending);
+        return resolver.insert(uri, values);
+    }
+
     public long pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement,
             BluetoothMapAppParams ap, String emailBaseUri)
             throws IllegalArgumentException, RemoteException, IOException {
@@ -2649,8 +2687,6 @@
                     String phone = recipient.getFirstPhoneNumber();
                     String email = recipient.getFirstEmail();
                     String folder = folderElement.getName();
-                    boolean read = false;
-                    boolean deliveryReport = true;
                     String msgBody = null;
 
                     /* If MMS contains text only and the size is less than ten SMS's
@@ -2699,8 +2735,8 @@
                         Uri contentUri = Uri.parse(Sms.CONTENT_URI + "/" + folder);
                         Uri uri;
                         synchronized (getMsgListSms()) {
-                            uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody, "",
-                                    System.currentTimeMillis(), read, deliveryReport);
+                            uri = addMessageToUri(mResolver, contentUri, phone, msgBody, "",
+                                    System.currentTimeMillis());
 
                             if (V) {
                                 Log.v(TAG, "Sms.addMessageToUri() returned: " + uri);
@@ -3272,7 +3308,7 @@
                     Log.d(TAG, "actionMessageSent: result OK");
                 }
                 if (msgInfo.transparent == 0) {
-                    if (!Sms.moveMessageToFolder(context, msgInfo.uri, Sms.MESSAGE_TYPE_SENT, 0)) {
+                    if (!Utils.moveMessageToFolder(context, msgInfo.uri, true)) {
                         Log.w(TAG, "Failed to move " + msgInfo.uri + " to SENT");
                     }
                 } else {
@@ -3294,8 +3330,7 @@
                     sendEvent(evt);
                 } else {
                     if (msgInfo.transparent == 0) {
-                        if (!Sms.moveMessageToFolder(context, msgInfo.uri, Sms.MESSAGE_TYPE_FAILED,
-                                0)) {
+                        if (!Utils.moveMessageToFolder(context, msgInfo.uri, false)) {
                             Log.w(TAG, "Failed to move " + msgInfo.uri + " to FAILED");
                         }
                     } else {
@@ -3509,7 +3544,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 (!Utils.moveMessageToFolder(context, uri, true)) {
                     Log.d(TAG, "Failed to move " + uri + " to SENT");
                 }
             } else {
@@ -3522,7 +3557,7 @@
             } else */
             {
                 if (transparent == 0) {
-                    if (!Sms.moveMessageToFolder(context, uri, Sms.MESSAGE_TYPE_FAILED, 0)) {
+                    if (!Utils.moveMessageToFolder(context, uri, false)) {
                         Log.d(TAG, "Failed to move " + uri + " to FAILED");
                     }
                 } else {
@@ -3597,7 +3632,7 @@
                     if (msgInfo == null || !msgInfo.resend) {
                         continue;
                     }
-                    Sms.moveMessageToFolder(mContext, msgInfo.uri, Sms.MESSAGE_TYPE_FAILED, 0);
+                    Utils.moveMessageToFolder(mContext, msgInfo.uri, false);
                 } while (c.moveToNext());
             }
         } finally {
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListing.java b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
index f33ea64..83a299e 100644
--- a/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
@@ -17,8 +17,8 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.bluetooth.Utils;
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -91,7 +91,7 @@
      */
     public byte[] encode() throws UnsupportedEncodingException {
         StringWriter sw = new StringWriter();
-        XmlSerializer xmlConvoElement = new FastXmlSerializer();
+        XmlSerializer xmlConvoElement = new FastXmlSerializer(0);
         try {
             xmlConvoElement.setOutput(sw);
             xmlConvoElement.startDocument("UTF-8", true);
@@ -156,7 +156,7 @@
                     if (D) {
                         Log.i(TAG, "Unknown XML tag: " + name);
                     }
-                    XmlUtils.skipCurrentTag(parser);
+                    Utils.skipCurrentTag(parser);
                 }
                 readConversations(parser);
             }
@@ -191,7 +191,7 @@
                 if (D) {
                     Log.i(TAG, "Unknown XML tag: " + name);
                 }
-                XmlUtils.skipCurrentTag(parser);
+                Utils.skipCurrentTag(parser);
                 continue;
             }
             // Add a single conversation
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
index be28be0..26a7fda 100644
--- a/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
@@ -17,8 +17,8 @@
 import android.util.Log;
 
 import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
-import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -341,7 +341,7 @@
                 if (D) {
                     Log.i(TAG, "Unknown XML tag: " + name);
                 }
-                XmlUtils.skipCurrentTag(parser);
+                Utils.skipCurrentTag(parser);
                 continue;
             }
         }
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
index 560558e..b256211 100644
--- a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -17,8 +17,8 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.bluetooth.Utils;
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -266,7 +266,7 @@
 
     public byte[] encode(int offset, int count) throws UnsupportedEncodingException {
         StringWriter sw = new StringWriter();
-        XmlSerializer xmlMsgElement = new FastXmlSerializer();
+        XmlSerializer xmlMsgElement = new FastXmlSerializer(0);
         int i, stopIndex;
         // We need index based access to the subFolders
         BluetoothMapFolderElement[] folders =
@@ -346,7 +346,7 @@
                     if (D) {
                         Log.i(TAG, "Unknown XML tag: " + name);
                     }
-                    XmlUtils.skipCurrentTag(parser);
+                    Utils.skipCurrentTag(parser);
                 }
                 readFolders(parser);
             }
@@ -378,7 +378,7 @@
                 if (D) {
                     Log.i(TAG, "Unknown XML tag: " + name);
                 }
-                XmlUtils.skipCurrentTag(parser);
+                Utils.skipCurrentTag(parser);
                 continue;
             }
             int count = parser.getAttributeCount();
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index e3df91f..4755dfd 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothObexTransport;
@@ -51,10 +52,16 @@
     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 String BLUETOOTH_MAP_VERSION_PROPERTY = "persist.bluetooth.mapversion";
+
+    private static final int SDP_MAP_MAS_VERSION_1_2 = 0x0102;
+    private static final int SDP_MAP_MAS_VERSION_1_3 = 0x0103;
+    private static final int SDP_MAP_MAS_VERSION_1_4 = 0x0104;
 
     /* TODO: Should these be adaptive for each MAS? - e.g. read from app? */
-    static final int SDP_MAP_MAS_FEATURES = 0x0000007F;
+    static final int SDP_MAP_MAS_FEATURES_1_2 = 0x0000007F;
+    static final int SDP_MAP_MAS_FEATURES_1_3 = 0x000603FF;
+    static final int SDP_MAP_MAS_FEATURES_1_4 = 0x000603FF;
 
     private ServerSession mServerSession = null;
     // The handle to the socket registration with SDP
@@ -80,7 +87,7 @@
     private int mMasInstanceId = -1;
     private boolean mEnableSmsMms = false;
     BluetoothMapContentObserver mObserver;
-
+    private BluetoothMapObexServer mMapServer;
     private AtomicLong mDbIndetifier = new AtomicLong();
     private AtomicLong mFolderVersionCounter = new AtomicLong(0);
     private AtomicLong mSmsMmsConvoListVersionCounter = new AtomicLong(0);
@@ -99,6 +106,7 @@
             new HashMap<Long, BluetoothMapConvoListingElement>();
 
     private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
+    private static int sFeatureMask = SDP_MAP_MAS_FEATURES_1_2;
 
     public static final String TYPE_SMS_MMS_STR = "SMS/MMS";
     public static final String TYPE_EMAIL_STR = "EMAIL";
@@ -339,9 +347,30 @@
             }
         }
 
+        final String currentValue = SystemProperties.get(BLUETOOTH_MAP_VERSION_PROPERTY, "map12");
+        int masVersion;
+
+        switch (currentValue) {
+            case "map12":
+                masVersion = SDP_MAP_MAS_VERSION_1_2;
+                sFeatureMask = SDP_MAP_MAS_FEATURES_1_2;
+                break;
+            case "map13":
+                masVersion = SDP_MAP_MAS_VERSION_1_3;
+                sFeatureMask = SDP_MAP_MAS_FEATURES_1_3;
+                break;
+            case "map14":
+                masVersion = SDP_MAP_MAS_VERSION_1_4;
+                sFeatureMask = SDP_MAP_MAS_FEATURES_1_4;
+                break;
+            default:
+                masVersion = SDP_MAP_MAS_VERSION_1_2;
+                sFeatureMask = SDP_MAP_MAS_FEATURES_1_2;
+        }
+
         return SdpManager.getDefaultManager()
                 .createMapMasRecord(masName, mMasInstanceId, rfcommChannel, l2capPsm,
-                        SDP_MAP_MAS_VERSION, messageTypeFlags, SDP_MAP_MAS_FEATURES);
+                        masVersion, messageTypeFlags, sFeatureMask);
     }
 
     /* Called for all MAS instances for each instance when auth. is completed, hence
@@ -360,17 +389,16 @@
             }
 
             mMnsClient = mnsClient;
-            BluetoothMapObexServer mapServer;
             mObserver = new BluetoothMapContentObserver(mContext, mMnsClient, this, mAccount,
                     mEnableSmsMms);
             mObserver.init();
-            mapServer =
+            mMapServer =
                     new BluetoothMapObexServer(mServiceHandler, mContext, mObserver, this, mAccount,
                             mEnableSmsMms);
-
+            mMapServer.setRemoteFeatureMask(mRemoteFeatureMask);
             // setup transport
             BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
-            mServerSession = new ServerSession(transport, mapServer, null);
+            mServerSession = new ServerSession(transport, mMapServer, null);
             if (D) {
                 Log.d(mTag, "    ServerSession started.");
             }
@@ -456,9 +484,11 @@
         if (V) {
             Log.v(mTag, "setRemoteFeatureMask : Curr: " + mRemoteFeatureMask);
         }
-        mRemoteFeatureMask = supportedFeatures & SDP_MAP_MAS_FEATURES;
+        mRemoteFeatureMask = supportedFeatures & sFeatureMask;
+        BluetoothMapUtils.savePeerSupportUtcTimeStamp(mRemoteFeatureMask);
         if (mObserver != null) {
             mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
+            mMapServer.setRemoteFeatureMask(mRemoteFeatureMask);
             if (V) {
                 Log.v(mTag, "setRemoteFeatureMask : set: " + mRemoteFeatureMask);
             }
@@ -469,6 +499,10 @@
         return this.mRemoteFeatureMask;
     }
 
+    public static int getFeatureMask() {
+        return sFeatureMask;
+    }
+
     @Override
     public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
         if (!mAcceptNewConnections) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
index dbe3b8c..9fc4dfc 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
@@ -103,7 +103,7 @@
                 xmlMsgElement.setOutput(sw);
                 xmlMsgElement.text("\n");
             } else {
-                xmlMsgElement = new FastXmlSerializer();
+                xmlMsgElement = new FastXmlSerializer(0);
                 xmlMsgElement.setOutput(sw);
                 xmlMsgElement.startDocument("UTF-8", true);
                 xmlMsgElement.setFeature(
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
index 17fc549..8e8f216 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
@@ -288,7 +288,8 @@
         }
 
         if (mDateTime != 0) {
-            xmlMsgElement.attribute(null, "datetime", this.getDateTimeString());
+            xmlMsgElement.attribute(null, "datetime",
+                    BluetoothMapUtils.getDateTimeString(this.getDateTime()));
         }
         if (mSenderName != null) {
             xmlMsgElement.attribute(null, "sender_name",
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index 3b35262..15a7840 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -324,6 +324,11 @@
         }
         this.mRemoteFeatureMask = mRemoteFeatureMask;
         this.mOutContent.setRemoteFeatureMask(mRemoteFeatureMask);
+        if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT)
+                == BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT) {
+            mMessageVersion = BluetoothMapUtils.MAP_V11_STR;
+        }
+        if (V) Log.d(TAG," setRemoteFeatureMask mMessageVersion :" + mMessageVersion);
     }
 
     @Override
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index 1d651ba..ee2e3c2 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -36,6 +36,7 @@
 import android.os.ParcelUuid;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -585,18 +586,18 @@
         }
     }
 
-    boolean setPriority(BluetoothDevice device, int priority) {
+    boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         if (VERBOSE) {
-            Log.v(TAG, "Saved priority " + device + " = " + priority);
+            Log.v(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
         AdapterService.getAdapterService().getDatabase()
-                .setProfilePriority(device, BluetoothProfile.MAP, priority);
+                .setProfileConnectionPolicy(device, BluetoothProfile.MAP, connectionPolicy);
         return true;
     }
 
-    int getPriority(BluetoothDevice device) {
+    int getConnectionPolicy(BluetoothDevice device) {
         return AdapterService.getAdapterService().getDatabase()
-                .getProfilePriority(device, BluetoothProfile.MAP);
+                .getProfileConnectionPolicy(device, BluetoothProfile.MAP);
     }
 
     @Override
@@ -637,7 +638,8 @@
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         mAppObserver = new BluetoothMapAppObserver(this, this);
 
-        mSmsCapable = getResources().getBoolean(com.android.internal.R.bool.config_sms_capable);
+        TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+        mSmsCapable = tm.isSmsCapable();
 
         mEnabledAccounts = mAppObserver.getEnabledAccountItems();
         createMasInstances();  // Uses mEnabledAccounts
@@ -1258,21 +1260,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             BluetoothMapService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             BluetoothMapService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
     }
 
diff --git a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
index f6af8db..3473b7a 100644
--- a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
@@ -16,16 +16,15 @@
 
 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
 
+import android.content.Context;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
-import com.android.internal.telephony.GsmAlphabet;
+import com.android.bluetooth.util.GsmAlphabet;
 import com.android.internal.telephony.SmsConstants;
-import com.android.internal.telephony.SmsHeader;
-import com.android.internal.telephony.cdma.sms.BearerData;
-import com.android.internal.telephony.cdma.sms.UserData;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -45,6 +44,18 @@
     public static final int SMS_TYPE_GSM = 1;
     public static final int SMS_TYPE_CDMA = 2;
 
+    /**
+     * from SMS user data header information element identifiers.
+     * (see TS 23.040 9.2.3.24)
+     */
+    private static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT     = 0x24;
+    private static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT    = 0x25;
+
+    /**
+     * Supported message types for CDMA SMS messages
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
+     */
+    private static final int MESSAGE_TYPE_DELIVER = 0x01;
 
     /* We need to handle the SC-address mentioned in errata 4335.
      * Since the definition could be read in three different ways, I have asked
@@ -259,7 +270,7 @@
                 // Mask out the type
                 tmp &= 0x0f;
                 // Set the new type
-                tmp |= ((BearerData.MESSAGE_TYPE_DELIVER << 4) & 0xf0);
+                tmp |= ((MESSAGE_TYPE_DELIVER << 4) & 0xf0);
                 // Store the result
                 mData[offset + 2] = (byte) tmp;
 
@@ -341,9 +352,9 @@
                     } catch (IOException e) {
                         Log.w(TAG, "unable to read userDataHeader", e);
                     }
-                    SmsHeader userDataHeader = SmsHeader.fromByteArray(udh);
-                    mLanguageTable = userDataHeader.languageTable;
-                    mLanguageShiftTable = userDataHeader.languageShiftTable;
+                    int[] tableValue = getTableFromByteArray(udh);
+                    mLanguageTable = tableValue[0];
+                    mLanguageShiftTable = tableValue[1];
 
                     int headerBits = (userDataHeaderLength + 1) * 8;
                     int headerSeptets = headerBits / 7;
@@ -502,37 +513,34 @@
         return sConcatenatedRef;
     }
 
-    public static ArrayList<SmsPdu> getSubmitPdus(String messageText, String address) {
+    public static ArrayList<SmsPdu> getSubmitPdus(Context context, 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 = context.getSystemService(TelephonyManager.class)
+                .getCurrentPhoneType();
         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);
+        int[] ted = SmsMessage.calculateLength((CharSequence) messageText, false);
 
         SmsPdu newPdu;
         String destinationAddress;
-        int msgCount = ted.msgCount;
+        int msgCount = ted[0];
         int encoding;
         int languageTable;
         int languageShiftTable;
         int refNumber = getNextConcatenatedRef() & 0x00FF;
-        ArrayList<String> smsFragments = SmsMessage.fragmentText(messageText);
+        SmsManager smsMng = SmsManager.getDefault();
+        ArrayList<String> smsFragments = smsMng.divideMessage(messageText);
         ArrayList<SmsPdu> pdus = new ArrayList<SmsPdu>(msgCount);
         byte[] data;
 
         // Default to GSM, as this code should not be used, if we neither have CDMA not GSM.
         phoneType = (activePhone == PHONE_TYPE_CDMA) ? SMS_TYPE_CDMA : SMS_TYPE_GSM;
-        encoding = ted.codeUnitSize;
-        languageTable = ted.languageTable;
-        languageShiftTable = ted.languageShiftTable;
+        encoding = ted[3];
+        languageTable = ted[4];
+        languageShiftTable = ted[5];
         destinationAddress = PhoneNumberUtils.stripSeparators(address);
         if (destinationAddress == null || destinationAddress.length() < 2) {
             destinationAddress =
@@ -548,48 +556,9 @@
             /* 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++) {
-                SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
-                concatRef.refNumber = refNumber;
-                concatRef.seqNumber = i + 1;  // 1-based sequence
-                concatRef.msgCount = msgCount;
-                // We currently set this to true since our messaging app will never
-                // send more than 255 parts (it converts the message to MMS well before that).
-                // However, we should support 3rd party messaging apps that might need 16-bit
-                // references
-                // Note:  It's not sufficient to just flip this bit to true; it will have
-                // ripple effects (several calculations assume 8-bit ref).
-                concatRef.isEightBits = true;
-                SmsHeader smsHeader = new SmsHeader();
-                smsHeader.concatRef = concatRef;
-
-                /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding
-                 * will be determined(again) by getSubmitPdu().
-                 * All packets need to be encoded using the same encoding, as the bMessage
-                 * only have one filed to describe the encoding for all messages in a concatenated
-                 * SMS... */
-                if (encoding == SmsConstants.ENCODING_7BIT) {
-                    smsHeader.languageTable = languageTable;
-                    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;
-                } else { // SMS_TYPE_CDMA
-                    UserData uData = new UserData();
-                    uData.payloadStr = smsFragments.get(i);
-                    uData.userDataHeader = smsHeader;
-                    if (encoding == SmsConstants.ENCODING_7BIT) {
-                        uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
-                    } else { // assume UTF-16
-                        uData.msgEncoding = UserData.ENCODING_UNICODE_16;
-                    }
-                    uData.msgEncodingSet = true;
-                    data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
-                            destinationAddress, uData, false).encodedMessage;
-                }
+                data = SmsMessage.getSubmitPduEncodedMessage(phoneType == SMS_TYPE_GSM,
+                    destinationAddress, smsFragments.get(i), encoding, languageTable,
+                    languageShiftTable, refNumber, i + 1, msgCount);
                 newPdu = new SmsPdu(data, encoding, phoneType, languageTable);
                 pdus.add(newPdu);
             }
@@ -607,8 +576,9 @@
      * @param date The delivery time stamp.
      * @return
      */
-    public static ArrayList<SmsPdu> getDeliverPdus(String messageText, String address, long date) {
-        ArrayList<SmsPdu> deliverPdus = getSubmitPdus(messageText, address);
+    public static ArrayList<SmsPdu> getDeliverPdus(Context context, String messageText,
+            String address, long date) {
+        ArrayList<SmsPdu> deliverPdus = getSubmitPdus(context, messageText, address);
 
         /*
          * For CDMA the only difference between deliver and submit pdus are the messageType,
@@ -635,11 +605,13 @@
      * The destination address must be extracted from the bmessage vCard(s).
      */
     public static String decodePdu(byte[] data, int type) {
-        String ret;
+        String ret = "";
         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();
+            SmsMessage smsMessage = SmsMessage.createFromNativeSmsSubmitPdu(data, true);
+            if (smsMessage != null) {
+                ret = smsMessage.getMessageBody();
+            }
         } else {
             /* For GSM, there is no submit pdu decoder, and most parser utils are private, and
             only minded for submit pdus */
@@ -786,4 +758,26 @@
         return messageBody;
     }
 
+    private static int[] getTableFromByteArray(byte[] data) {
+        ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+        /** tableValue[0]: languageTable
+         *  tableValue[1]: languageShiftTable */
+        int[] tableValue = new int[2];
+        while (inStream.available() > 0) {
+            int id = inStream.read();
+            int length = inStream.read();
+            switch (id) {
+                case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
+                    tableValue[1] = inStream.read();
+                    break;
+                case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
+                    tableValue[0] = inStream.read();
+                    break;
+                default:
+                    inStream.skip(length);
+            }
+        }
+        return tableValue;
+    }
+
 }
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index e867bd6..a3710a3 100644
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -27,7 +27,7 @@
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.BitSet;
-import java.util.Date;
+import java.util.Calendar;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -96,6 +96,8 @@
     static final int MAP_MESSAGE_LISTING_FORMAT_V10 = 10; // MAP spec below 1.3
     static final int MAP_MESSAGE_LISTING_FORMAT_V11 = 11; // MAP spec 1.3
 
+    private static boolean mPeerSupportUtcTimeStamp = false;
+
     /**
      * 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.
@@ -112,13 +114,6 @@
         }
     }
 
-    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
-    }
-
-
     public static void printCursor(Cursor c) {
         if (D) {
             StringBuilder sb = new StringBuilder();
@@ -671,5 +666,26 @@
         }
     }
 
+
+    static String getDateTimeString( long timestamp) {
+        SimpleDateFormat format = (mPeerSupportUtcTimeStamp) ? new
+            SimpleDateFormat("yyyyMMdd'T'HHmmssZ") : new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(timestamp);
+        if (V) Log.v(TAG, "getDateTimeString  timestamp :" + timestamp + " time:"
+                + format.format(cal.getTime()));
+        return format.format(cal.getTime());
+    }
+
+    static void savePeerSupportUtcTimeStamp(int remoteFeatureMask) {
+        if ((remoteFeatureMask & MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT)
+                == MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT) {
+            mPeerSupportUtcTimeStamp = true;
+        } else {
+            mPeerSupportUtcTimeStamp = false;
+        }
+        if (V) Log.v(TAG, "savePeerSupportUtcTimeStamp " + mPeerSupportUtcTimeStamp);
+    }
+
 }
 
diff --git a/src/com/android/bluetooth/mapclient/MapClientService.java b/src/com/android/bluetooth/mapclient/MapClientService.java
index 9467683..96b8efb 100644
--- a/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -101,8 +101,9 @@
             Log.d(TAG, "MAP connect device: " + device
                     + ", InstanceMap start state: " + sb.toString());
         }
-        if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
-            Log.w(TAG, "Connection not allowed: <" + device.getAddress() + "> is PRIORITY_OFF");
+        if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            Log.w(TAG, "Connection not allowed: <" + device.getAddress()
+                    + "> is CONNECTION_POLICY_FORBIDDEN");
             return false;
         }
         MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
@@ -217,18 +218,50 @@
                 : mapStateMachine.getState();
     }
 
-    public boolean setPriority(BluetoothDevice device, int priority) {
+    /**
+     * Set connection policy of the profile and connects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         if (VDBG) {
-            Log.v(TAG, "Saved priority " + device + " = " + priority);
+            Log.v(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
         AdapterService.getAdapterService().getDatabase()
-                .setProfilePriority(device, BluetoothProfile.MAP_CLIENT, priority);
+                .setProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT, connectionPolicy);
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
         return true;
     }
 
-    public int getPriority(BluetoothDevice device) {
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    public int getConnectionPolicy(BluetoothDevice device) {
         return AdapterService.getAdapterService().getDatabase()
-                .getProfilePriority(device, BluetoothProfile.MAP_CLIENT);
+                .getProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT);
     }
 
     public synchronized boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
@@ -484,21 +517,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             MapClientService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             MapClientService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
 
         @Override
diff --git a/src/com/android/bluetooth/mapclient/MasClient.java b/src/com/android/bluetooth/mapclient/MasClient.java
index 6991704..2966f21 100644
--- a/src/com/android/bluetooth/mapclient/MasClient.java
+++ b/src/com/android/bluetooth/mapclient/MasClient.java
@@ -26,7 +26,7 @@
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothObexTransport;
-import com.android.internal.util.StateMachine;
+import com.android.bluetooth.statemachine.StateMachine;
 
 import java.io.IOException;
 import java.lang.ref.WeakReference;
diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java
index 9b86aae..85ba60e 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -59,10 +59,10 @@
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.map.BluetoothMapbMessageMime;
+import com.android.bluetooth.statemachine.IState;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IState;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
 import com.android.vcard.VCardConstants;
 import com.android.vcard.VCardEntry;
 import com.android.vcard.VCardProperty;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBatch.java b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
index a66a667..b2c9cd4 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBatch.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
@@ -37,8 +37,6 @@
 import android.content.Context;
 import android.util.Log;
 
-import com.google.android.collect.Lists;
-
 import java.io.File;
 import java.util.ArrayList;
 
@@ -105,7 +103,7 @@
     public BluetoothOppBatch(Context context, BluetoothOppShareInfo info) {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         mContext = context;
-        mShares = Lists.newArrayList();
+        mShares = new ArrayList();
         mTimestamp = info.mTimestamp;
         mDirection = info.mDirection;
         mDestination = adapter.getRemoteDevice(info.mDestination);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
index 53b785f..cc24c8d 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
@@ -21,7 +21,6 @@
 import android.content.IntentFilter;
 import android.content.pm.ProviderInfo;
 import android.net.Uri;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
@@ -68,8 +67,7 @@
             if (!mRegisteredReceiver) {
                 IntentFilter userFilter = new IntentFilter();
                 userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
-                mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.CURRENT, userFilter,
-                        null, null);
+                mContext.registerReceiver(mBroadcastReceiver, userFilter, null, null);
                 mRegisteredReceiver = true;
             }
             UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index 914b9b6..d084dd6 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -61,8 +61,6 @@
 import com.android.bluetooth.sdp.SdpManager;
 import com.android.internal.annotations.VisibleForTesting;
 
-import com.google.android.collect.Lists;
-
 import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -196,8 +194,8 @@
         if (V) {
             Log.v(TAG, "onCreate");
         }
-        mShares = Lists.newArrayList();
-        mBatches = Lists.newArrayList();
+        mShares = new ArrayList();
+        mBatches = new ArrayList();
         mBatchId = 1;
         final ContentResolver contentResolver = getContentResolver();
         new Thread("trimDatabase") {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 06562e4..94c2fa3 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -140,9 +140,9 @@
                 ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
                 if (D) {
                     Log.d(TAG, "Received UUID: " + uuid.toString());
-                    Log.d(TAG, "expected UUID: " + BluetoothUuid.ObexObjectPush.toString());
+                    Log.d(TAG, "expected UUID: " + BluetoothUuid.OBEX_OBJECT_PUSH.toString());
                 }
-                if (uuid.equals(BluetoothUuid.ObexObjectPush)) {
+                if (uuid.equals(BluetoothUuid.OBEX_OBJECT_PUSH)) {
                     int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
                     Log.d(TAG, " -> status: " + status);
                     BluetoothDevice device =
@@ -637,7 +637,7 @@
 
     private void startConnectSession() {
         mDevice = mBatch.mDestination;
-        if (!mBatch.mDestination.sdpSearch(BluetoothUuid.ObexObjectPush)) {
+        if (!mBatch.mDestination.sdpSearch(BluetoothUuid.OBEX_OBJECT_PUSH)) {
             if (D) {
                 Log.d(TAG, "SDP failed, start rfcomm connect directly");
             }
@@ -722,7 +722,7 @@
                     return;
                 }
                 mBtSocket = mDevice.createInsecureRfcommSocketToServiceRecord(
-                        BluetoothUuid.ObexObjectPush.getUuid());
+                        BluetoothUuid.OBEX_OBJECT_PUSH.getUuid());
             } catch (IOException e1) {
                 Log.e(TAG, "Rfcomm socket create error", e1);
                 markConnectionFailed(mBtSocket);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 7533d0d..2f89635 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -50,8 +50,6 @@
 
 import com.android.bluetooth.R;
 
-import com.google.android.collect.Lists;
-
 import java.io.File;
 import java.io.IOException;
 import java.math.RoundingMode;
@@ -147,7 +145,7 @@
      */
     // 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();
+        ArrayList<String> uris = new ArrayList();
         final String where = BluetoothShare.TIMESTAMP + " == " + timeStamp;
         Cursor metadataCursor =
                 context.getContentResolver().query(BluetoothShare.CONTENT_URI, new String[]{
diff --git a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
index 72016f7..4037682 100644
--- a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
+++ b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
@@ -32,7 +32,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.text.TextUtils;
-import android.util.Slog;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -95,7 +95,7 @@
                             .withoutIpReachabilityMonitor()
                             .build().toStableParcelable());
                 } catch (RemoteException e) {
-                    Slog.e(TAG, "Error starting IpClient provisioning", e);
+                    Log.e(TAG, "Error starting IpClient provisioning", e);
                 }
             }
         }
@@ -117,7 +117,7 @@
             try {
                 mIpClient.shutdown();
             } catch (RemoteException e) {
-                Slog.e(TAG, "Error shutting down IpClient", e);
+                Log.e(TAG, "Error shutting down IpClient", e);
             }
             mIpClient = null;
         }
@@ -146,7 +146,7 @@
 
                 synchronized (BluetoothTetheringNetworkFactory.this) {
                     if (TextUtils.isEmpty(mInterfaceName)) {
-                        Slog.e(TAG, "attempted to reverse tether without interface name");
+                        Log.e(TAG, "attempted to reverse tether without interface name");
                         return;
                     }
                     log("ipProvisioningThread(+" + mInterfaceName + "): " + "mNetworkInfo="
@@ -157,7 +157,7 @@
 
                 linkProperties = ipcCallback.waitForProvisioning();
                 if (linkProperties == null) {
-                    Slog.e(TAG, "IP provisioning error.");
+                    Log.e(TAG, "IP provisioning error.");
                     synchronized (BluetoothTetheringNetworkFactory.this) {
                         stopIpClientLocked();
                         setScoreFilter(-1);
@@ -212,12 +212,12 @@
     // becomes available.  We register our NetworkFactory at this point.
     public void startReverseTether(final String iface) {
         if (iface == null || TextUtils.isEmpty(iface)) {
-            Slog.e(TAG, "attempted to reverse tether with empty interface");
+            Log.e(TAG, "attempted to reverse tether with empty interface");
             return;
         }
         synchronized (this) {
             if (!TextUtils.isEmpty(mInterfaceName)) {
-                Slog.e(TAG, "attempted to reverse tether while already in process");
+                Log.e(TAG, "attempted to reverse tether while already in process");
                 return;
             }
             mInterfaceName = iface;
@@ -231,7 +231,7 @@
     // goes away.  We stop advertising ourselves to ConnectivityService at this point.
     public synchronized void stopReverseTether() {
         if (TextUtils.isEmpty(mInterfaceName)) {
-            Slog.e(TAG, "attempted to stop reverse tether with nothing tethered");
+            Log.e(TAG, "attempted to stop reverse tether with nothing tethered");
             return;
         }
         onCancelRequest();
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index aa5a1fd..2818441 100644
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -18,15 +18,17 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothPan.LocalPanRole;
+import android.bluetooth.BluetoothPan.RemotePanRole;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothPan;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources.NotFoundException;
 import android.net.ConnectivityManager;
+import android.net.InetAddresses;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -248,6 +250,15 @@
         }
 
         @Override
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+            PanService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setConnectionPolicy(device, connectionPolicy);
+        }
+
+        @Override
         public int getConnectionState(BluetoothDevice device) {
             PanService service = getService();
             if (service == null) {
@@ -359,6 +370,7 @@
 
     public boolean isTetheringOn() {
         // TODO(BT) have a variable marking the on/off state
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mTetherOn;
     }
 
@@ -385,26 +397,54 @@
         }
     }
 
-    public boolean setPriority(BluetoothDevice device, int priority) {
-        if (device == null) {
-            throw new IllegalArgumentException("Null device");
-        }
+    /**
+     * Set connection policy of the profile and connects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         if (DBG) {
-            Log.d(TAG, "Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        AdapterService.getAdapterService().getDatabase()
-                .setProfilePriority(device, BluetoothProfile.PAN, priority);
-        return true;
+        boolean setSuccessfully;
+        setSuccessfully = AdapterService.getAdapterService().getDatabase()
+                .setProfileConnectionPolicy(device, BluetoothProfile.PAN, connectionPolicy);
+        if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (setSuccessfully
+                && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
+        return setSuccessfully;
     }
 
-    public int getPriority(BluetoothDevice device) {
-        if (device == null) {
-            throw new IllegalArgumentException("Null device");
-        }
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         return AdapterService.getAdapterService().getDatabase()
-                .getProfilePriority(device, BluetoothProfile.PAN);
+                .getProfileConnectionPolicy(device, BluetoothProfile.PAN);
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
@@ -485,8 +525,8 @@
         }
     }
 
-    void handlePanDeviceStateChange(BluetoothDevice device, String iface, int state, int localRole,
-            int remoteRole) {
+    void handlePanDeviceStateChange(BluetoothDevice device, String iface, int state,
+            @LocalPanRole int localRole, @RemotePanRole int remoteRole) {
         if (DBG) {
             Log.d(TAG, "handlePanDeviceStateChange: device: " + device + ", iface: " + iface
                     + ", state: " + state + ", localRole:" + localRole + ", remoteRole:"
@@ -625,10 +665,10 @@
                 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"))) {
+                        InetAddresses.parseNumericAddress("0.0.0.0")) || addr.equals(
+                        InetAddresses.parseNumericAddress("::0"))) {
                     address = BLUETOOTH_IFACE_ADDR_START;
-                    addr = NetworkUtils.numericToInetAddress(address);
+                    addr = InetAddresses.parseNumericAddress(address);
                 }
 
                 ifcg.setLinkAddress(new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH));
@@ -637,8 +677,6 @@
                 } else {
                     ifcg.setInterfaceDown();
                 }
-
-                ifcg.clearFlag("running");
                 service.setInterfaceConfig(iface, ifcg);
 
                 if (enable) {
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index 466ecc4..04680fa 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -50,7 +50,6 @@
 import android.os.PowerManager;
 import android.os.UserManager;
 import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.bluetooth.IObexConnectionHandler;
@@ -427,7 +426,8 @@
      * {@link BluetoothProfile#STATE_DISCONNECTING}
      */
     public int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        enforceCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
         if (mPbapStateMachineMap == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
         }
@@ -469,7 +469,38 @@
         return devices;
     }
 
-    void disconnect(BluetoothDevice device) {
+    /**
+     * Disconnects Pbap if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy determines whether pbap should be disconnected
+     * @return true if pbap is disconnected, false otherwise
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+        enforceCallingOrSelfPermission(
+                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
+        if (DEBUG) {
+            Log.d(TAG, "setConnectionPolicy: device " + device
+                    + " and connectionPolicy " + connectionPolicy);
+        }
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Disconnects pbap server profile with device
+     * @param device is the remote bluetooth device
+     */
+    public void disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         synchronized (mPbapStateMachineMap) {
             PbapStateMachine sm = mPbapStateMachineMap.get(device);
@@ -654,6 +685,19 @@
         }
 
         @Override
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+            if (DEBUG) {
+                Log.d(TAG, "setConnectionPolicy for device: " + device + ", policy:"
+                        + connectionPolicy);
+            }
+            BluetoothPbapService service = getService();
+            if (service == null) {
+                return false;
+            }
+            return service.setConnectionPolicy(device, connectionPolicy);
+        }
+
+        @Override
         public void disconnect(BluetoothDevice device) {
             if (DEBUG) {
                 Log.d(TAG, "disconnect");
@@ -786,10 +830,7 @@
         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);
-            }
+            sLocalPhoneName = this.getString(R.string.localPhoneName);
         }
         if (VERBOSE)
             Log.v(TAG, "Local Phone Details- Number:" + sLocalPhoneNum
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 53670ff..4e4a240 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -99,7 +99,7 @@
     private static final long PBAP_FILTER_NICKNAME = 1 << 23;
 
     private static final int PBAP_SUPPORTED_FEATURE =
-            PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_BROWSING | PBAP_FEATURE_DOWNLOADING;
+            PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | 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_EMAIL | PBAP_FILTER_TEL | PBAP_FILTER_NICKNAME;
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java
index 16b02e1..9446af1 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -19,9 +19,11 @@
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothPbapClient;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -31,6 +33,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
 import com.android.bluetooth.sdp.SdpManager;
 
 import java.util.ArrayList;
@@ -71,6 +74,9 @@
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         // delay initial download until after the user is unlocked to add an account.
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        // To remove call logs when PBAP was never connected while calls were made,
+        // we also listen for HFP to become disconnected.
+        filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
         try {
             registerReceiver(mPbapBroadcastReceiver, filter);
         } catch (Exception e) {
@@ -128,6 +134,21 @@
         }
     }
 
+    private void removeHfpCallLog(String accountName, Context context) {
+        if (DBG) Log.d(TAG, "Removing call logs from " + accountName);
+        // Delete call logs belonging to accountName==BD_ADDR that also match
+        // component name "hfpclient".
+        ComponentName componentName = new ComponentName(context, HfpClientConnectionService.class);
+        String selectionFilter = CallLog.Calls.PHONE_ACCOUNT_ID + "=? AND "
+                + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=?";
+        String[] selectionArgs = new String[]{accountName, componentName.flattenToString()};
+        try {
+            getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs);
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
+        }
+    }
+
     private void registerSdpRecord() {
         SdpManager sdpManager = SdpManager.getDefaultManager();
         if (sdpManager == null) {
@@ -171,6 +192,21 @@
                 for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
                     stateMachine.resumeDownload();
                 }
+            } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) {
+                // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects.
+                // However, if PBAP was never connected/enabled in the first place, and calls are
+                // made over HFP, these calllogs will not be removed when the device disconnects.
+                // This code ensures callogs are still removed in this case.
+                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+                if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+                    if (DBG) {
+                        Log.d(TAG, "Received intent to disconnect HFP with " + device);
+                    }
+                    // HFP client stores entries in calllog.db by BD_ADDR and component name
+                    removeHfpCallLog(device.getAddress(), context);
+                }
             }
         }
     }
@@ -253,21 +289,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             PbapClientService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             PbapClientService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
 
 
@@ -299,7 +335,7 @@
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
-        if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
+        if (getConnectionPolicy(device) <= BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             return false;
         }
         synchronized (mPbapClientStateMachineMap) {
@@ -375,26 +411,58 @@
         }
     }
 
-    public boolean setPriority(BluetoothDevice device, int priority) {
+    /**
+     * Set connection policy of the profile and connects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         if (device == null) {
             throw new IllegalArgumentException("Null device");
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         if (DBG) {
-            Log.d(TAG, "Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
         AdapterService.getAdapterService().getDatabase()
-            .setProfilePriority(device, BluetoothProfile.PBAP_CLIENT, priority);
+            .setProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT, connectionPolicy);
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
         return true;
     }
 
-    public int getPriority(BluetoothDevice device) {
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (device == null) {
             throw new IllegalArgumentException("Null device");
         }
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         return AdapterService.getAdapterService().getDatabase()
-                .getProfilePriority(device, BluetoothProfile.PBAP_CLIENT);
+                .getProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT);
     }
 
     @Override
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
index 2eaf8e5..71e6c67 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
@@ -59,9 +59,9 @@
 import com.android.bluetooth.BluetoothMetricsProto;
 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 com.android.bluetooth.statemachine.IState;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index 995051e..bd02812 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -595,18 +595,47 @@
         }
     }
 
-    public boolean setPriority(BluetoothDevice device, int priority) {
+    /**
+     * Set connection policy of the profile and disconnects it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     */
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         if (DEBUG) {
-            Log.d(TAG, "Saved priority " + device + " = " + priority);
+            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
         AdapterService.getAdapterService().getDatabase()
-                .setProfilePriority(device, BluetoothProfile.SAP, priority);
+                .setProfileConnectionPolicy(device, BluetoothProfile.SAP, connectionPolicy);
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
         return true;
     }
 
-    public int getPriority(BluetoothDevice device) {
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    public int getConnectionPolicy(BluetoothDevice device) {
         return AdapterService.getAdapterService().getDatabase()
-                .getProfilePriority(device, BluetoothProfile.SAP);
+                .getProfileConnectionPolicy(device, BluetoothProfile.SAP);
     }
 
     @Override
@@ -801,8 +830,9 @@
                             Log.v(TAG, "setSimAccessPermission(ACCESS_ALLOWED) result=" + result);
                         }
                     }
-                    boolean result = setPriority(mRemoteDevice, BluetoothProfile.PRIORITY_ON);
-                    Log.d(TAG, "setPriority ON, result = " + result);
+                    boolean result = setConnectionPolicy(mRemoteDevice,
+                            BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+                    Log.d(TAG, "setConnectionPolicy ALLOWED, result = " + result);
 
                     try {
                         if (mConnSocket != null) {
@@ -822,8 +852,9 @@
                             Log.v(TAG, "setSimAccessPermission(ACCESS_REJECTED) result=" + result);
                         }
                     }
-                    boolean result = setPriority(mRemoteDevice, BluetoothProfile.PRIORITY_OFF);
-                    Log.d(TAG, "setPriority OFF, result = " + result);
+                    boolean result = setConnectionPolicy(mRemoteDevice,
+                            BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+                    Log.d(TAG, "setConnectionPolicy FORBIDDEN, result = " + result);
                     // Ensure proper cleanup, and prepare for new connect.
                     mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
                 }
@@ -983,21 +1014,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int priority) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
             SapService service = getService();
             if (service == null) {
                 return false;
             }
-            return service.setPriority(device, priority);
+            return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
+        public int getConnectionPolicy(BluetoothDevice device) {
             SapService service = getService();
             if (service == null) {
-                return BluetoothProfile.PRIORITY_UNDEFINED;
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            return service.getPriority(device);
+            return service.getConnectionPolicy(device);
         }
     }
 }
diff --git a/src/com/android/bluetooth/statemachine/IState.java b/src/com/android/bluetooth/statemachine/IState.java
new file mode 100644
index 0000000..559975f
--- /dev/null
+++ b/src/com/android/bluetooth/statemachine/IState.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.statemachine;
+
+import android.os.Message;
+
+/**
+ * @hide
+ *
+ * The interface for implementing states in a {@link StateMachine}
+ */
+public interface IState {
+
+    /**
+     * Returned by processMessage to indicate the message was processed.
+     */
+    static final boolean HANDLED = true;
+
+    /**
+     * Returned by processMessage to indicate the message was NOT processed.
+     */
+    static final boolean NOT_HANDLED = false;
+
+    /**
+     * Called when a state is entered.
+     */
+    void enter();
+
+    /**
+     * Called when a state is exited.
+     */
+    void exit();
+
+    /**
+     * Called when a message is to be processed by the
+     * state machine.
+     *
+     * This routine is never reentered thus no synchronization
+     * is needed as only one processMessage method will ever be
+     * executing within a state machine at any given time. This
+     * does mean that processing by this routine must be completed
+     * as expeditiously as possible as no subsequent messages will
+     * be processed until this routine returns.
+     *
+     * @param msg to process
+     * @return HANDLED if processing has completed and NOT_HANDLED
+     *         if the message wasn't processed.
+     */
+    boolean processMessage(Message msg);
+
+    /**
+     * Name of State for debugging purposes.
+     *
+     * @return name of state.
+     */
+    String getName();
+}
diff --git a/src/com/android/bluetooth/statemachine/State.java b/src/com/android/bluetooth/statemachine/State.java
new file mode 100644
index 0000000..358938f
--- /dev/null
+++ b/src/com/android/bluetooth/statemachine/State.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.statemachine;
+
+import android.os.Message;
+
+/**
+ *  @hide
+ *
+ * The class for implementing states in a StateMachine
+ */
+public class State implements IState {
+
+    /**
+     * Constructor
+     */
+    protected State() {
+    }
+
+    @Override
+    public void enter() {
+    }
+
+    @Override
+    public void exit() {
+    }
+
+    @Override
+    public boolean processMessage(Message msg) {
+        return false;
+    }
+
+    /**
+     * Name of State for debugging purposes.
+     *
+     * This default implementation returns the class name, returning
+     * the instance name would better in cases where a State class
+     * is used for multiple states. But normally there is one class per
+     * state and the class name is sufficient and easy to get. You may
+     * want to provide a setName or some other mechanism for setting
+     * another name if the class name is not appropriate.
+     *
+     */
+    @Override
+    public String getName() {
+        String name = getClass().getName();
+        int lastDollar = name.lastIndexOf('$');
+        return name.substring(lastDollar + 1);
+    }
+}
diff --git a/src/com/android/bluetooth/statemachine/StateMachine.java b/src/com/android/bluetooth/statemachine/StateMachine.java
new file mode 100644
index 0000000..a21b59c
--- /dev/null
+++ b/src/com/android/bluetooth/statemachine/StateMachine.java
@@ -0,0 +1,2167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.statemachine;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+
+/**
+ *
+ * @hide
+ *
+ * <p>The state machine defined here is a hierarchical state machine which processes messages
+ * and can have states arranged hierarchically.</p>
+ *
+ * <p>A state is a <code>State</code> object and must implement
+ * <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
+ * The enter/exit methods are equivalent to the construction and destruction
+ * in Object Oriented programming and are used to perform initialization and
+ * cleanup of the state respectively. The <code>getName</code> method returns the
+ * name of the state; the default implementation returns the class name. It may be
+ * desirable to have <code>getName</code> return the state instance name instead,
+ * in particular if a particular state class has multiple instances.</p>
+ *
+ * <p>When a state machine is created, <code>addState</code> is used to build the
+ * hierarchy and <code>setInitialState</code> is used to identify which of these
+ * is the initial state. After construction the programmer calls <code>start</code>
+ * which initializes and starts the state machine. The first action the StateMachine
+ * is to the invoke <code>enter</code> for all of the initial state's hierarchy,
+ * starting at its eldest parent. The calls to enter will be done in the context
+ * of the StateMachine's Handler, not in the context of the call to start, and they
+ * will be invoked before any messages are processed. For example, given the simple
+ * state machine below, mP1.enter will be invoked and then mS1.enter. Finally,
+ * messages sent to the state machine will be processed by the current state;
+ * in our simple state machine below that would initially be mS1.processMessage.</p>
+ <pre>
+ mP1
+ /   \
+ mS2   mS1 ----&gt; initial state
+ </pre>
+ * <p>After the state machine is created and started, messages are sent to a state
+ * machine using <code>sendMessage</code> and the messages are created using
+ * <code>obtainMessage</code>. When the state machine receives a message the
+ * current state's <code>processMessage</code> is invoked. In the above example
+ * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code>
+ * to change the current state to a new state.</p>
+ *
+ * <p>Each state in the state machine may have a zero or one parent states. If
+ * a child state is unable to handle a message it may have the message processed
+ * by its parent by returning false or NOT_HANDLED. If a message is not handled by
+ * a child state or any of its ancestors, <code>unhandledMessage</code> will be invoked
+ * to give one last chance for the state machine to process the message.</p>
+ *
+ * <p>When all processing is completed a state machine may choose to call
+ * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code>
+ * returns the state machine will transfer to an internal <code>HaltingState</code>
+ * and invoke <code>halting</code>. Any message subsequently received by the state
+ * machine will cause <code>haltedProcessMessage</code> to be invoked.</p>
+ *
+ * <p>If it is desirable to completely stop the state machine call <code>quit</code> or
+ * <code>quitNow</code>. These will call <code>exit</code> of the current state and its parents,
+ * call <code>onQuitting</code> and then exit Thread/Loopers.</p>
+ *
+ * <p>In addition to <code>processMessage</code> each <code>State</code> has
+ * an <code>enter</code> method and <code>exit</code> method which may be overridden.</p>
+ *
+ * <p>Since the states are arranged in a hierarchy transitioning to a new state
+ * causes current states to be exited and new states to be entered. To determine
+ * the list of states to be entered/exited the common parent closest to
+ * the current state is found. We then exit from the current state and its
+ * parent's up to but not including the common parent state and then enter all
+ * of the new states below the common parent down to the destination state.
+ * If there is no common parent all states are exited and then the new states
+ * are entered.</p>
+ *
+ * <p>Two other methods that states can use are <code>deferMessage</code> and
+ * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends
+ * a message but places it on the front of the queue rather than the back. The
+ * <code>deferMessage</code> causes the message to be saved on a list until a
+ * transition is made to a new state. At which time all of the deferred messages
+ * will be put on the front of the state machine queue with the oldest message
+ * at the front. These will then be processed by the new current state before
+ * any other messages that are on the queue or might be added later. Both of
+ * these are protected and may only be invoked from within a state machine.</p>
+ *
+ * <p>To illustrate some of these properties we'll use state machine with an 8
+ * state hierarchy:</p>
+ <pre>
+ mP0
+ /   \
+ mP1   mS0
+ /   \
+ mS2   mS1
+ /  \    \
+ mS3  mS4  mS5  ---&gt; initial state
+ </pre>
+ * <p>After starting mS5 the list of active states is mP0, mP1, mS1 and mS5.
+ * So the order of calling processMessage when a message is received is mS5,
+ * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this
+ * message by returning false or NOT_HANDLED.</p>
+ *
+ * <p>Now assume mS5.processMessage receives a message it can handle, and during
+ * the handling determines the machine should change states. It could call
+ * transitionTo(mS4) and return true or HANDLED. Immediately after returning from
+ * processMessage the state machine runtime will find the common parent,
+ * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then
+ * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
+ * when the next message is received mS4.processMessage will be invoked.</p>
+ *
+ * <p>Now for some concrete examples, here is the canonical HelloWorld as a state machine.
+ * It responds with "Hello World" being printed to the log for every message.</p>
+ <pre>
+ class HelloWorld extends StateMachine {
+ HelloWorld(String name) {
+ super(name);
+ addState(mState1);
+ setInitialState(mState1);
+ }
+
+ public static HelloWorld makeHelloWorld() {
+ HelloWorld hw = new HelloWorld("hw");
+ hw.start();
+ return hw;
+ }
+
+ class State1 extends State {
+ &#64;Override public boolean processMessage(Message message) {
+ log("Hello World");
+ return HANDLED;
+ }
+ }
+ State1 mState1 = new State1();
+ }
+
+ void testHelloWorld() {
+ HelloWorld hw = makeHelloWorld();
+ hw.sendMessage(hw.obtainMessage());
+ }
+ </pre>
+ * <p>A more interesting state machine is one with four states
+ * with two independent parent states.</p>
+ <pre>
+ mP1      mP2
+ /   \
+ mS2   mS1
+ </pre>
+ * <p>Here is a description of this state machine using pseudo code.</p>
+ <pre>
+ state mP1 {
+ enter { log("mP1.enter"); }
+ exit { log("mP1.exit");  }
+ on msg {
+ CMD_2 {
+ send(CMD_3);
+ defer(msg);
+ transitionTo(mS2);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ INITIAL
+ state mS1 parent mP1 {
+ enter { log("mS1.enter"); }
+ exit  { log("mS1.exit");  }
+ on msg {
+ CMD_1 {
+ transitionTo(mS1);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ state mS2 parent mP1 {
+ enter { log("mS2.enter"); }
+ exit  { log("mS2.exit");  }
+ on msg {
+ CMD_2 {
+ send(CMD_4);
+ return HANDLED;
+ }
+ CMD_3 {
+ defer(msg);
+ transitionTo(mP2);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ state mP2 {
+ enter {
+ log("mP2.enter");
+ send(CMD_5);
+ }
+ exit { log("mP2.exit"); }
+ on msg {
+ CMD_3, CMD_4 { return HANDLED; }
+ CMD_5 {
+ transitionTo(HaltingState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+ </pre>
+ * <p>The implementation is below and also in StateMachineTest:</p>
+ <pre>
+ class Hsm1 extends StateMachine {
+ public static final int CMD_1 = 1;
+ public static final int CMD_2 = 2;
+ public static final int CMD_3 = 3;
+ public static final int CMD_4 = 4;
+ public static final int CMD_5 = 5;
+
+ public static Hsm1 makeHsm1() {
+ log("makeHsm1 E");
+ Hsm1 sm = new Hsm1("hsm1");
+ sm.start();
+ log("makeHsm1 X");
+ return sm;
+ }
+
+ Hsm1(String name) {
+ super(name);
+ log("ctor E");
+
+ // Add states, use indentation to show hierarchy
+ addState(mP1);
+ addState(mS1, mP1);
+ addState(mS2, mP1);
+ addState(mP2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ log("ctor X");
+ }
+
+ class P1 extends State {
+ &#64;Override public void enter() {
+ log("mP1.enter");
+ }
+ &#64;Override public boolean processMessage(Message message) {
+ boolean retVal;
+ log("mP1.processMessage what=" + message.what);
+ switch(message.what) {
+ case CMD_2:
+ // CMD_2 will arrive in mS2 before CMD_3
+ sendMessage(obtainMessage(CMD_3));
+ deferMessage(message);
+ transitionTo(mS2);
+ retVal = HANDLED;
+ break;
+ default:
+ // Any message we don't understand in this state invokes unhandledMessage
+ retVal = NOT_HANDLED;
+ break;
+ }
+ return retVal;
+ }
+ &#64;Override public void exit() {
+ log("mP1.exit");
+ }
+ }
+
+ class S1 extends State {
+ &#64;Override public void enter() {
+ log("mS1.enter");
+ }
+ &#64;Override public boolean processMessage(Message message) {
+ log("S1.processMessage what=" + message.what);
+ if (message.what == CMD_1) {
+ // Transition to ourself to show that enter/exit is called
+ transitionTo(mS1);
+ return HANDLED;
+ } else {
+ // Let parent process all other messages
+ return NOT_HANDLED;
+ }
+ }
+ &#64;Override public void exit() {
+ log("mS1.exit");
+ }
+ }
+
+ class S2 extends State {
+ &#64;Override public void enter() {
+ log("mS2.enter");
+ }
+ &#64;Override public boolean processMessage(Message message) {
+ boolean retVal;
+ log("mS2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_2):
+ sendMessage(obtainMessage(CMD_4));
+ retVal = HANDLED;
+ break;
+ case(CMD_3):
+ deferMessage(message);
+ transitionTo(mP2);
+ retVal = HANDLED;
+ break;
+ default:
+ retVal = NOT_HANDLED;
+ break;
+ }
+ return retVal;
+ }
+ &#64;Override public void exit() {
+ log("mS2.exit");
+ }
+ }
+
+ class P2 extends State {
+ &#64;Override public void enter() {
+ log("mP2.enter");
+ sendMessage(obtainMessage(CMD_5));
+ }
+ &#64;Override public boolean processMessage(Message message) {
+ log("P2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_3):
+ break;
+ case(CMD_4):
+ break;
+ case(CMD_5):
+ transitionToHaltingState();
+ break;
+ }
+ return HANDLED;
+ }
+ &#64;Override public void exit() {
+ log("mP2.exit");
+ }
+ }
+
+ &#64;Override
+ void onHalting() {
+ log("halting");
+ synchronized (this) {
+ this.notifyAll();
+ }
+ }
+
+ P1 mP1 = new P1();
+ S1 mS1 = new S1();
+ S2 mS2 = new S2();
+ P2 mP2 = new P2();
+ }
+ </pre>
+ * <p>If this is executed by sending two messages CMD_1 and CMD_2
+ * (Note the synchronize is only needed because we use hsm.wait())</p>
+ <pre>
+ Hsm1 hsm = makeHsm1();
+ synchronize(hsm) {
+ hsm.sendMessage(obtainMessage(hsm.CMD_1));
+ hsm.sendMessage(obtainMessage(hsm.CMD_2));
+ try {
+ // wait for the messages to be handled
+ hsm.wait();
+ } catch (InterruptedException e) {
+ loge("exception while waiting " + e.getMessage());
+ }
+ }
+ </pre>
+ * <p>The output is:</p>
+ <pre>
+ D/hsm1    ( 1999): makeHsm1 E
+ D/hsm1    ( 1999): ctor E
+ D/hsm1    ( 1999): ctor X
+ D/hsm1    ( 1999): mP1.enter
+ D/hsm1    ( 1999): mS1.enter
+ D/hsm1    ( 1999): makeHsm1 X
+ D/hsm1    ( 1999): mS1.processMessage what=1
+ D/hsm1    ( 1999): mS1.exit
+ D/hsm1    ( 1999): mS1.enter
+ D/hsm1    ( 1999): mS1.processMessage what=2
+ D/hsm1    ( 1999): mP1.processMessage what=2
+ D/hsm1    ( 1999): mS1.exit
+ D/hsm1    ( 1999): mS2.enter
+ D/hsm1    ( 1999): mS2.processMessage what=2
+ D/hsm1    ( 1999): mS2.processMessage what=3
+ D/hsm1    ( 1999): mS2.exit
+ D/hsm1    ( 1999): mP1.exit
+ D/hsm1    ( 1999): mP2.enter
+ D/hsm1    ( 1999): mP2.processMessage what=3
+ D/hsm1    ( 1999): mP2.processMessage what=4
+ D/hsm1    ( 1999): mP2.processMessage what=5
+ D/hsm1    ( 1999): mP2.exit
+ D/hsm1    ( 1999): halting
+ </pre>
+ */
+public class StateMachine {
+    // Name of the state machine and used as logging tag
+    private String mName;
+
+    /** Message.what value when quitting */
+    private static final int SM_QUIT_CMD = -1;
+
+    /** Message.what value when initializing */
+    private static final int SM_INIT_CMD = -2;
+
+    /**
+     * Convenience constant that maybe returned by processMessage
+     * to indicate the message was processed and is not to be
+     * processed by parent states
+     */
+    public static final boolean HANDLED = true;
+
+    /**
+     * Convenience constant that maybe returned by processMessage
+     * to indicate the message was NOT processed and is to be
+     * processed by parent states
+     */
+    public static final boolean NOT_HANDLED = false;
+
+    /**
+     * StateMachine logging record.
+     */
+    public static class LogRec {
+        private StateMachine mSm;
+        private long mTime;
+        private int mWhat;
+        private String mInfo;
+        private IState mState;
+        private IState mOrgState;
+        private IState mDstState;
+
+        /**
+         * Constructor
+         *
+         * @param msg
+         * @param state the state which handled the message
+         * @param orgState is the first state the received the message but
+         * did not processes the message.
+         * @param transToState is the state that was transitioned to after the message was
+         * processed.
+         */
+        LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState,
+                IState transToState) {
+            update(sm, msg, info, state, orgState, transToState);
+        }
+
+        /**
+         * Update the information in the record.
+         * @param state that handled the message
+         * @param orgState is the first state the received the message
+         * @param dstState is the state that was the transition target when logging
+         */
+        public void update(StateMachine sm, Message msg, String info, IState state, IState orgState,
+                IState dstState) {
+            mSm = sm;
+            mTime = System.currentTimeMillis();
+            mWhat = (msg != null) ? msg.what : 0;
+            mInfo = info;
+            mState = state;
+            mOrgState = orgState;
+            mDstState = dstState;
+        }
+
+        /**
+         * @return time stamp
+         */
+        public long getTime() {
+            return mTime;
+        }
+
+        /**
+         * @return msg.what
+         */
+        public long getWhat() {
+            return mWhat;
+        }
+
+        /**
+         * @return the command that was executing
+         */
+        public String getInfo() {
+            return mInfo;
+        }
+
+        /**
+         * @return the state that handled this message
+         */
+        public IState getState() {
+            return mState;
+        }
+
+        /**
+         * @return the state destination state if a transition is occurring or null if none.
+         */
+        public IState getDestState() {
+            return mDstState;
+        }
+
+        /**
+         * @return the original state that received the message.
+         */
+        public IState getOriginalState() {
+            return mOrgState;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("time=");
+            Calendar c = Calendar.getInstance();
+            c.setTimeInMillis(mTime);
+            sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
+            sb.append(" processed=");
+            sb.append(mState == null ? "<null>" : mState.getName());
+            sb.append(" org=");
+            sb.append(mOrgState == null ? "<null>" : mOrgState.getName());
+            sb.append(" dest=");
+            sb.append(mDstState == null ? "<null>" : mDstState.getName());
+            sb.append(" what=");
+            String what = mSm != null ? mSm.getWhatToString(mWhat) : "";
+            if (TextUtils.isEmpty(what)) {
+                sb.append(mWhat);
+                sb.append("(0x");
+                sb.append(Integer.toHexString(mWhat));
+                sb.append(")");
+            } else {
+                sb.append(what);
+            }
+            if (!TextUtils.isEmpty(mInfo)) {
+                sb.append(" ");
+                sb.append(mInfo);
+            }
+            return sb.toString();
+        }
+    }
+
+    /**
+     * A list of log records including messages recently processed by the state machine.
+     *
+     * The class maintains a list of log records including messages
+     * recently processed. The list is finite and may be set in the
+     * constructor or by calling setSize. The public interface also
+     * includes size which returns the number of recent records,
+     * count which is the number of records processed since the
+     * the last setSize, get which returns a record and
+     * add which adds a record.
+     */
+    private static class LogRecords {
+
+        private static final int DEFAULT_SIZE = 20;
+
+        private Vector<LogRec> mLogRecVector = new Vector<LogRec>();
+        private int mMaxSize = DEFAULT_SIZE;
+        private int mOldestIndex = 0;
+        private int mCount = 0;
+        private boolean mLogOnlyTransitions = false;
+
+        /**
+         * private constructor use add
+         */
+        private LogRecords() {
+        }
+
+        /**
+         * Set size of messages to maintain and clears all current records.
+         *
+         * @param maxSize number of records to maintain at anyone time.
+         */
+        synchronized void setSize(int maxSize) {
+            // TODO: once b/28217358 is fixed, add unit tests  to verify that these variables are
+            // cleared after calling this method, and that subsequent calls to get() function as
+            // expected.
+            mMaxSize = maxSize;
+            mOldestIndex = 0;
+            mCount = 0;
+            mLogRecVector.clear();
+        }
+
+        synchronized void setLogOnlyTransitions(boolean enable) {
+            mLogOnlyTransitions = enable;
+        }
+
+        synchronized boolean logOnlyTransitions() {
+            return mLogOnlyTransitions;
+        }
+
+        /**
+         * @return the number of recent records.
+         */
+        synchronized int size() {
+            return mLogRecVector.size();
+        }
+
+        /**
+         * @return the total number of records processed since size was set.
+         */
+        synchronized int count() {
+            return mCount;
+        }
+
+        /**
+         * Clear the list of records.
+         */
+        synchronized void cleanup() {
+            mLogRecVector.clear();
+        }
+
+        /**
+         * @return the information on a particular record. 0 is the oldest
+         * record and size()-1 is the newest record. If the index is to
+         * large null is returned.
+         */
+        synchronized LogRec get(int index) {
+            int nextIndex = mOldestIndex + index;
+            if (nextIndex >= mMaxSize) {
+                nextIndex -= mMaxSize;
+            }
+            if (nextIndex >= size()) {
+                return null;
+            } else {
+                return mLogRecVector.get(nextIndex);
+            }
+        }
+
+        /**
+         * Add a processed message.
+         *
+         * @param msg
+         * @param messageInfo to be stored
+         * @param state that handled the message
+         * @param orgState is the first state the received the message but
+         * did not processes the message.
+         * @param transToState is the state that was transitioned to after the message was
+         * processed.
+         *
+         */
+        synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state,
+                IState orgState, IState transToState) {
+            mCount += 1;
+            if (mLogRecVector.size() < mMaxSize) {
+                mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState));
+            } else {
+                LogRec pmi = mLogRecVector.get(mOldestIndex);
+                mOldestIndex += 1;
+                if (mOldestIndex >= mMaxSize) {
+                    mOldestIndex = 0;
+                }
+                pmi.update(sm, msg, messageInfo, state, orgState, transToState);
+            }
+        }
+    }
+
+    private static class SmHandler extends Handler {
+
+        /** true if StateMachine has quit */
+        private boolean mHasQuit = false;
+
+        /** The debug flag */
+        private boolean mDbg = false;
+
+        /** The SmHandler object, identifies that message is internal */
+        private static final Object mSmHandlerObj = new Object();
+
+        /** The current message */
+        private Message mMsg;
+
+        /** A list of log records including messages this state machine has processed */
+        private LogRecords mLogRecords = new LogRecords();
+
+        /** true if construction of the state machine has not been completed */
+        private boolean mIsConstructionCompleted;
+
+        /** Stack used to manage the current hierarchy of states */
+        private StateInfo mStateStack[];
+
+        /** Top of mStateStack */
+        private int mStateStackTopIndex = -1;
+
+        /** A temporary stack used to manage the state stack */
+        private StateInfo mTempStateStack[];
+
+        /** The top of the mTempStateStack */
+        private int mTempStateStackCount;
+
+        /** State used when state machine is halted */
+        private HaltingState mHaltingState = new HaltingState();
+
+        /** State used when state machine is quitting */
+        private QuittingState mQuittingState = new QuittingState();
+
+        /** Reference to the StateMachine */
+        private StateMachine mSm;
+
+        /**
+         * Information about a state.
+         * Used to maintain the hierarchy.
+         */
+        private class StateInfo {
+            /** The state */
+            State state;
+
+            /** The parent of this state, null if there is no parent */
+            StateInfo parentStateInfo;
+
+            /** True when the state has been entered and on the stack */
+            boolean active;
+
+            /**
+             * Convert StateInfo to string
+             */
+            @Override
+            public String toString() {
+                return "state=" + state.getName() + ",active=" + active + ",parent="
+                        + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
+            }
+        }
+
+        /** The map of all of the states in the state machine */
+        private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
+
+        /** The initial state that will process the first message */
+        private State mInitialState;
+
+        /** The destination state when transitionTo has been invoked */
+        private State mDestState;
+
+        /**
+         * Indicates if a transition is in progress
+         *
+         * This will be true for all calls of State.exit and all calls of State.enter except for the
+         * last enter call for the current destination state.
+         */
+        private boolean mTransitionInProgress = false;
+
+        /** The list of deferred messages */
+        private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
+
+        /**
+         * State entered when transitionToHaltingState is called.
+         */
+        private class HaltingState extends State {
+            @Override
+            public boolean processMessage(Message msg) {
+                mSm.haltedProcessMessage(msg);
+                return true;
+            }
+        }
+
+        /**
+         * State entered when a valid quit message is handled.
+         */
+        private class QuittingState extends State {
+            @Override
+            public boolean processMessage(Message msg) {
+                return NOT_HANDLED;
+            }
+        }
+
+        /**
+         * Handle messages sent to the state machine by calling
+         * the current state's processMessage. It also handles
+         * the enter/exit calls and placing any deferred messages
+         * back onto the queue when transitioning to a new state.
+         */
+        @Override
+        public final void handleMessage(Message msg) {
+            if (!mHasQuit) {
+                if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
+                    mSm.onPreHandleMessage(msg);
+                }
+
+                if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
+
+                /** Save the current message */
+                mMsg = msg;
+
+                /** State that processed the message */
+                State msgProcessedState = null;
+                if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
+                    /** Normal path */
+                    msgProcessedState = processMsg(msg);
+                } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
+                        && (mMsg.obj == mSmHandlerObj)) {
+                    /** Initial one time path. */
+                    mIsConstructionCompleted = true;
+                    invokeEnterMethods(0);
+                } else {
+                    throw new RuntimeException("StateMachine.handleMessage: "
+                            + "The start method not called, received msg: " + msg);
+                }
+                performTransitions(msgProcessedState, msg);
+
+                // We need to check if mSm == null here as we could be quitting.
+                if (mDbg && mSm != null) mSm.log("handleMessage: X");
+
+                if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
+                    mSm.onPostHandleMessage(msg);
+                }
+            }
+        }
+
+        /**
+         * Do any transitions
+         * @param msgProcessedState is the state that processed the message
+         */
+        private void performTransitions(State msgProcessedState, Message msg) {
+            /**
+             * If transitionTo has been called, exit and then enter
+             * the appropriate states. We loop on this to allow
+             * enter and exit methods to use transitionTo.
+             */
+            State orgState = mStateStack[mStateStackTopIndex].state;
+
+            /**
+             * Record whether message needs to be logged before we transition and
+             * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
+             * always set msg.obj to the handler.
+             */
+            boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);
+
+            if (mLogRecords.logOnlyTransitions()) {
+                /** Record only if there is a transition */
+                if (mDestState != null) {
+                    mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,
+                            orgState, mDestState);
+                }
+            } else if (recordLogMsg) {
+                /** Record message */
+                mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,
+                        mDestState);
+            }
+
+            State destState = mDestState;
+            if (destState != null) {
+                /**
+                 * Process the transitions including transitions in the enter/exit methods
+                 */
+                while (true) {
+                    if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
+
+                    /**
+                     * Determine the states to exit and enter and return the
+                     * common ancestor state of the enter/exit states. Then
+                     * invoke the exit methods then the enter methods.
+                     */
+                    StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
+                    // flag is cleared in invokeEnterMethods before entering the target state
+                    mTransitionInProgress = true;
+                    invokeExitMethods(commonStateInfo);
+                    int stateStackEnteringIndex = moveTempStateStackToStateStack();
+                    invokeEnterMethods(stateStackEnteringIndex);
+
+                    /**
+                     * Since we have transitioned to a new state we need to have
+                     * any deferred messages moved to the front of the message queue
+                     * so they will be processed before any other messages in the
+                     * message queue.
+                     */
+                    moveDeferredMessageAtFrontOfQueue();
+
+                    if (destState != mDestState) {
+                        // A new mDestState so continue looping
+                        destState = mDestState;
+                    } else {
+                        // No change in mDestState so we're done
+                        break;
+                    }
+                }
+                mDestState = null;
+            }
+
+            /**
+             * After processing all transitions check and
+             * see if the last transition was to quit or halt.
+             */
+            if (destState != null) {
+                if (destState == mQuittingState) {
+                    /**
+                     * Call onQuitting to let subclasses cleanup.
+                     */
+                    mSm.onQuitting();
+                    cleanupAfterQuitting();
+                } else if (destState == mHaltingState) {
+                    /**
+                     * Call onHalting() if we've transitioned to the halting
+                     * state. All subsequent messages will be processed in
+                     * in the halting state which invokes haltedProcessMessage(msg);
+                     */
+                    mSm.onHalting();
+                }
+            }
+        }
+
+        /**
+         * Cleanup all the static variables and the looper after the SM has been quit.
+         */
+        private final void cleanupAfterQuitting() {
+            if (mSm.mSmThread != null) {
+                // If we made the thread then quit looper which stops the thread.
+                getLooper().quit();
+                mSm.mSmThread = null;
+            }
+
+            mSm.mSmHandler = null;
+            mSm = null;
+            mMsg = null;
+            mLogRecords.cleanup();
+            mStateStack = null;
+            mTempStateStack = null;
+            mStateInfo.clear();
+            mInitialState = null;
+            mDestState = null;
+            mDeferredMessages.clear();
+            mHasQuit = true;
+        }
+
+        /**
+         * Complete the construction of the state machine.
+         */
+        private final void completeConstruction() {
+            if (mDbg) mSm.log("completeConstruction: E");
+
+            /**
+             * Determine the maximum depth of the state hierarchy
+             * so we can allocate the state stacks.
+             */
+            int maxDepth = 0;
+            for (StateInfo si : mStateInfo.values()) {
+                int depth = 0;
+                for (StateInfo i = si; i != null; depth++) {
+                    i = i.parentStateInfo;
+                }
+                if (maxDepth < depth) {
+                    maxDepth = depth;
+                }
+            }
+            if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
+
+            mStateStack = new StateInfo[maxDepth];
+            mTempStateStack = new StateInfo[maxDepth];
+            setupInitialStateStack();
+
+            /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
+            sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
+
+            if (mDbg) mSm.log("completeConstruction: X");
+        }
+
+        /**
+         * Process the message. If the current state doesn't handle
+         * it, call the states parent and so on. If it is never handled then
+         * call the state machines unhandledMessage method.
+         * @return the state that processed the message
+         */
+        private final State processMsg(Message msg) {
+            StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
+            if (mDbg) {
+                mSm.log("processMsg: " + curStateInfo.state.getName());
+            }
+
+            if (isQuit(msg)) {
+                transitionTo(mQuittingState);
+            } else {
+                while (!curStateInfo.state.processMessage(msg)) {
+                    /**
+                     * Not processed
+                     */
+                    curStateInfo = curStateInfo.parentStateInfo;
+                    if (curStateInfo == null) {
+                        /**
+                         * No parents left so it's not handled
+                         */
+                        mSm.unhandledMessage(msg);
+                        break;
+                    }
+                    if (mDbg) {
+                        mSm.log("processMsg: " + curStateInfo.state.getName());
+                    }
+                }
+            }
+            return (curStateInfo != null) ? curStateInfo.state : null;
+        }
+
+        /**
+         * Call the exit method for each state from the top of stack
+         * up to the common ancestor state.
+         */
+        private final void invokeExitMethods(StateInfo commonStateInfo) {
+            while ((mStateStackTopIndex >= 0)
+                    && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
+                State curState = mStateStack[mStateStackTopIndex].state;
+                if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
+                curState.exit();
+                mStateStack[mStateStackTopIndex].active = false;
+                mStateStackTopIndex -= 1;
+            }
+        }
+
+        /**
+         * Invoke the enter method starting at the entering index to top of state stack
+         */
+        private final void invokeEnterMethods(int stateStackEnteringIndex) {
+            for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
+                if (stateStackEnteringIndex == mStateStackTopIndex) {
+                    // Last enter state for transition
+                    mTransitionInProgress = false;
+                }
+                if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
+                mStateStack[i].state.enter();
+                mStateStack[i].active = true;
+            }
+            mTransitionInProgress = false; // ensure flag set to false if no methods called
+        }
+
+        /**
+         * Move the deferred message to the front of the message queue.
+         */
+        private final void moveDeferredMessageAtFrontOfQueue() {
+            /**
+             * The oldest messages on the deferred list must be at
+             * the front of the queue so start at the back, which
+             * as the most resent message and end with the oldest
+             * messages at the front of the queue.
+             */
+            for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
+                Message curMsg = mDeferredMessages.get(i);
+                if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
+                sendMessageAtFrontOfQueue(curMsg);
+            }
+            mDeferredMessages.clear();
+        }
+
+        /**
+         * Move the contents of the temporary stack to the state stack
+         * reversing the order of the items on the temporary stack as
+         * they are moved.
+         *
+         * @return index into mStateStack where entering needs to start
+         */
+        private final int moveTempStateStackToStateStack() {
+            int startingIndex = mStateStackTopIndex + 1;
+            int i = mTempStateStackCount - 1;
+            int j = startingIndex;
+            while (i >= 0) {
+                if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
+                mStateStack[j] = mTempStateStack[i];
+                j += 1;
+                i -= 1;
+            }
+
+            mStateStackTopIndex = j - 1;
+            if (mDbg) {
+                mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
+                        + ",startingIndex=" + startingIndex + ",Top="
+                        + mStateStack[mStateStackTopIndex].state.getName());
+            }
+            return startingIndex;
+        }
+
+        /**
+         * Setup the mTempStateStack with the states we are going to enter.
+         *
+         * This is found by searching up the destState's ancestors for a
+         * state that is already active i.e. StateInfo.active == true.
+         * The destStae and all of its inactive parents will be on the
+         * TempStateStack as the list of states to enter.
+         *
+         * @return StateInfo of the common ancestor for the destState and
+         * current state or null if there is no common parent.
+         */
+        private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
+            /**
+             * Search up the parent list of the destination state for an active
+             * state. Use a do while() loop as the destState must always be entered
+             * even if it is active. This can happen if we are exiting/entering
+             * the current state.
+             */
+            mTempStateStackCount = 0;
+            StateInfo curStateInfo = mStateInfo.get(destState);
+            do {
+                mTempStateStack[mTempStateStackCount++] = curStateInfo;
+                curStateInfo = curStateInfo.parentStateInfo;
+            } while ((curStateInfo != null) && !curStateInfo.active);
+
+            if (mDbg) {
+                mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
+                        + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
+            }
+            return curStateInfo;
+        }
+
+        /**
+         * Initialize StateStack to mInitialState.
+         */
+        private final void setupInitialStateStack() {
+            if (mDbg) {
+                mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
+            }
+
+            StateInfo curStateInfo = mStateInfo.get(mInitialState);
+            for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
+                mTempStateStack[mTempStateStackCount] = curStateInfo;
+                curStateInfo = curStateInfo.parentStateInfo;
+            }
+
+            // Empty the StateStack
+            mStateStackTopIndex = -1;
+
+            moveTempStateStackToStateStack();
+        }
+
+        /**
+         * @return current message
+         */
+        private final Message getCurrentMessage() {
+            return mMsg;
+        }
+
+        /**
+         * @return current state
+         */
+        private final IState getCurrentState() {
+            return mStateStack[mStateStackTopIndex].state;
+        }
+
+        /**
+         * Add a new state to the state machine. Bottom up addition
+         * of states is allowed but the same state may only exist
+         * in one hierarchy.
+         *
+         * @param state the state to add
+         * @param parent the parent of state
+         * @return stateInfo for this state
+         */
+        private final StateInfo addState(State state, State parent) {
+            if (mDbg) {
+                mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
+                        + ((parent == null) ? "" : parent.getName()));
+            }
+            StateInfo parentStateInfo = null;
+            if (parent != null) {
+                parentStateInfo = mStateInfo.get(parent);
+                if (parentStateInfo == null) {
+                    // Recursively add our parent as it's not been added yet.
+                    parentStateInfo = addState(parent, null);
+                }
+            }
+            StateInfo stateInfo = mStateInfo.get(state);
+            if (stateInfo == null) {
+                stateInfo = new StateInfo();
+                mStateInfo.put(state, stateInfo);
+            }
+
+            // Validate that we aren't adding the same state in two different hierarchies.
+            if ((stateInfo.parentStateInfo != null)
+                    && (stateInfo.parentStateInfo != parentStateInfo)) {
+                throw new RuntimeException("state already added");
+            }
+            stateInfo.state = state;
+            stateInfo.parentStateInfo = parentStateInfo;
+            stateInfo.active = false;
+            if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
+            return stateInfo;
+        }
+
+        /**
+         * Remove a state from the state machine. Will not remove the state if it is currently
+         * active or if it has any children in the hierarchy.
+         * @param state the state to remove
+         */
+        private void removeState(State state) {
+            StateInfo stateInfo = mStateInfo.get(state);
+            if (stateInfo == null || stateInfo.active) {
+                return;
+            }
+            boolean isParent = mStateInfo.values().stream()
+                    .filter(si -> si.parentStateInfo == stateInfo)
+                    .findAny()
+                    .isPresent();
+            if (isParent) {
+                return;
+            }
+            mStateInfo.remove(state);
+        }
+
+        /**
+         * Constructor
+         *
+         * @param looper for dispatching messages
+         * @param sm the hierarchical state machine
+         */
+        private SmHandler(Looper looper, StateMachine sm) {
+            super(looper);
+            mSm = sm;
+
+            addState(mHaltingState, null);
+            addState(mQuittingState, null);
+        }
+
+        /** @see StateMachine#setInitialState(State) */
+        private final void setInitialState(State initialState) {
+            if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
+            mInitialState = initialState;
+        }
+
+        /** @see StateMachine#transitionTo(IState) */
+        private final void transitionTo(IState destState) {
+            if (mTransitionInProgress) {
+                Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +
+                        mDestState + ", new target state=" + destState);
+            }
+            mDestState = (State) destState;
+            if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
+        }
+
+        /** @see StateMachine#deferMessage(Message) */
+        private final void deferMessage(Message msg) {
+            if (mDbg) mSm.log("deferMessage: msg=" + msg.what);
+
+            /* Copy the "msg" to "newMsg" as "msg" will be recycled */
+            Message newMsg = obtainMessage();
+            newMsg.copyFrom(msg);
+
+            mDeferredMessages.add(newMsg);
+        }
+
+        /** @see StateMachine#quit() */
+        private final void quit() {
+            if (mDbg) mSm.log("quit:");
+            sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
+        }
+
+        /** @see StateMachine#quitNow() */
+        private final void quitNow() {
+            if (mDbg) mSm.log("quitNow:");
+            sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
+        }
+
+        /** Validate that the message was sent by quit or quitNow. */
+        private final boolean isQuit(Message msg) {
+            return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj);
+        }
+
+        /** @see StateMachine#isDbg() */
+        private final boolean isDbg() {
+            return mDbg;
+        }
+
+        /** @see StateMachine#setDbg(boolean) */
+        private final void setDbg(boolean dbg) {
+            mDbg = dbg;
+        }
+
+    }
+
+    private SmHandler mSmHandler;
+    private HandlerThread mSmThread;
+
+    /**
+     * Initialize.
+     *
+     * @param looper for this state machine
+     * @param name of the state machine
+     */
+    private void initStateMachine(String name, Looper looper) {
+        mName = name;
+        mSmHandler = new SmHandler(looper, this);
+    }
+
+    /**
+     * Constructor creates a StateMachine with its own thread.
+     *
+     * @param name of the state machine
+     */
+    protected StateMachine(String name) {
+        mSmThread = new HandlerThread(name);
+        mSmThread.start();
+        Looper looper = mSmThread.getLooper();
+
+        initStateMachine(name, looper);
+    }
+
+    /**
+     * Constructor creates a StateMachine using the looper.
+     *
+     * @param name of the state machine
+     */
+    protected StateMachine(String name, Looper looper) {
+        initStateMachine(name, looper);
+    }
+
+    /**
+     * Constructor creates a StateMachine using the handler.
+     *
+     * @param name of the state machine
+     */
+    protected StateMachine(String name, Handler handler) {
+        initStateMachine(name, handler.getLooper());
+    }
+
+    /**
+     * Notifies subclass that the StateMachine handler is about to process the Message msg
+     * @param msg The message that is being handled
+     */
+    protected void onPreHandleMessage(Message msg) {
+    }
+
+    /**
+     * Notifies subclass that the StateMachine handler has finished processing the Message msg and
+     * has possibly transitioned to a new state.
+     * @param msg The message that is being handled
+     */
+    protected void onPostHandleMessage(Message msg) {
+    }
+
+    /**
+     * Add a new state to the state machine
+     * @param state the state to add
+     * @param parent the parent of state
+     */
+    public final void addState(State state, State parent) {
+        mSmHandler.addState(state, parent);
+    }
+
+    /**
+     * Add a new state to the state machine, parent will be null
+     * @param state to add
+     */
+    public final void addState(State state) {
+        mSmHandler.addState(state, null);
+    }
+
+    /**
+     * Removes a state from the state machine, unless it is currently active or if it has children.
+     * @param state state to remove
+     */
+    public final void removeState(State state) {
+        mSmHandler.removeState(state);
+    }
+
+    /**
+     * Set the initial state. This must be invoked before
+     * and messages are sent to the state machine.
+     *
+     * @param initialState is the state which will receive the first message.
+     */
+    public final void setInitialState(State initialState) {
+        mSmHandler.setInitialState(initialState);
+    }
+
+    /**
+     * @return current message
+     */
+    public final Message getCurrentMessage() {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return null;
+        return smh.getCurrentMessage();
+    }
+
+    /**
+     * @return current state
+     */
+    public final IState getCurrentState() {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return null;
+        return smh.getCurrentState();
+    }
+
+    /**
+     * transition to destination state. Upon returning
+     * from processMessage the current state's exit will
+     * be executed and upon the next message arriving
+     * destState.enter will be invoked.
+     *
+     * this function can also be called inside the enter function of the
+     * previous transition target, but the behavior is undefined when it is
+     * called mid-way through a previous transition (for example, calling this
+     * in the enter() routine of a intermediate node when the current transition
+     * target is one of the nodes descendants).
+     *
+     * @param destState will be the state that receives the next message.
+     */
+    public final void transitionTo(IState destState) {
+        mSmHandler.transitionTo(destState);
+    }
+
+    /**
+     * transition to halt state. Upon returning
+     * from processMessage we will exit all current
+     * states, execute the onHalting() method and then
+     * for all subsequent messages haltedProcessMessage
+     * will be called.
+     */
+    public final void transitionToHaltingState() {
+        mSmHandler.transitionTo(mSmHandler.mHaltingState);
+    }
+
+    /**
+     * Defer this message until next state transition.
+     * Upon transitioning all deferred messages will be
+     * placed on the queue and reprocessed in the original
+     * order. (i.e. The next state the oldest messages will
+     * be processed first)
+     *
+     * @param msg is deferred until the next transition.
+     */
+    public final void deferMessage(Message msg) {
+        mSmHandler.deferMessage(msg);
+    }
+
+    /**
+     * Called when message wasn't handled
+     *
+     * @param msg that couldn't be handled.
+     */
+    protected void unhandledMessage(Message msg) {
+        if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what);
+    }
+
+    /**
+     * Called for any message that is received after
+     * transitionToHalting is called.
+     */
+    protected void haltedProcessMessage(Message msg) {
+    }
+
+    /**
+     * This will be called once after handling a message that called
+     * transitionToHalting. All subsequent messages will invoke
+     * {@link StateMachine#haltedProcessMessage(Message)}
+     */
+    protected void onHalting() {
+    }
+
+    /**
+     * This will be called once after a quit message that was NOT handled by
+     * the derived StateMachine. The StateMachine will stop and any subsequent messages will be
+     * ignored. In addition, if this StateMachine created the thread, the thread will
+     * be stopped after this method returns.
+     */
+    protected void onQuitting() {
+    }
+
+    /**
+     * @return the name
+     */
+    public final String getName() {
+        return mName;
+    }
+
+    /**
+     * Set number of log records to maintain and clears all current records.
+     *
+     * @param maxSize number of messages to maintain at anyone time.
+     */
+    public final void setLogRecSize(int maxSize) {
+        mSmHandler.mLogRecords.setSize(maxSize);
+    }
+
+    /**
+     * Set to log only messages that cause a state transition
+     *
+     * @param enable {@code true} to enable, {@code false} to disable
+     */
+    public final void setLogOnlyTransitions(boolean enable) {
+        mSmHandler.mLogRecords.setLogOnlyTransitions(enable);
+    }
+
+    /**
+     * @return the number of log records currently readable
+     */
+    public final int getLogRecSize() {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return 0;
+        return smh.mLogRecords.size();
+    }
+
+    /**
+     * @return the number of log records we can store
+     */
+    @VisibleForTesting
+    public final int getLogRecMaxSize() {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return 0;
+        return smh.mLogRecords.mMaxSize;
+    }
+
+    /**
+     * @return the total number of records processed
+     */
+    public final int getLogRecCount() {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return 0;
+        return smh.mLogRecords.count();
+    }
+
+    /**
+     * @return a log record, or null if index is out of range
+     */
+    public final LogRec getLogRec(int index) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return null;
+        return smh.mLogRecords.get(index);
+    }
+
+    /**
+     * @return a copy of LogRecs as a collection
+     */
+    public final Collection<LogRec> copyLogRecs() {
+        Vector<LogRec> vlr = new Vector<LogRec>();
+        SmHandler smh = mSmHandler;
+        if (smh != null) {
+            for (LogRec lr : smh.mLogRecords.mLogRecVector) {
+                vlr.add(lr);
+            }
+        }
+        return vlr;
+    }
+
+    /**
+     * Add the string to LogRecords.
+     *
+     * @param string
+     */
+    public void addLogRec(String string) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+        smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(),
+                smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState);
+    }
+
+    /**
+     * @return true if msg should be saved in the log, default is true.
+     */
+    protected boolean recordLogRec(Message msg) {
+        return true;
+    }
+
+    /**
+     * Return a string to be logged by LogRec, default
+     * is an empty string. Override if additional information is desired.
+     *
+     * @param msg that was processed
+     * @return information to be logged as a String
+     */
+    protected String getLogRecString(Message msg) {
+        return "";
+    }
+
+    /**
+     * @return the string for msg.what
+     */
+    protected String getWhatToString(int what) {
+        return null;
+    }
+
+    /**
+     * @return Handler, maybe null if state machine has quit.
+     */
+    public final Handler getHandler() {
+        return mSmHandler;
+    }
+
+    /**
+     * Get a message and set Message.target state machine handler.
+     *
+     * Note: The handler can be null if the state machine has quit,
+     * which means target will be null and may cause a AndroidRuntimeException
+     * in MessageQueue#enqueMessage if sent directly or if sent using
+     * StateMachine#sendMessage the message will just be ignored.
+     *
+     * @return  A Message object from the global pool
+     */
+    public final Message obtainMessage() {
+        return Message.obtain(mSmHandler);
+    }
+
+    /**
+     * Get a message and set Message.target state machine handler, what.
+     *
+     * Note: The handler can be null if the state machine has quit,
+     * which means target will be null and may cause a AndroidRuntimeException
+     * in MessageQueue#enqueMessage if sent directly or if sent using
+     * StateMachine#sendMessage the message will just be ignored.
+     *
+     * @param what is the assigned to Message.what.
+     * @return  A Message object from the global pool
+     */
+    public final Message obtainMessage(int what) {
+        return Message.obtain(mSmHandler, what);
+    }
+
+    /**
+     * Get a message and set Message.target state machine handler,
+     * what and obj.
+     *
+     * Note: The handler can be null if the state machine has quit,
+     * which means target will be null and may cause a AndroidRuntimeException
+     * in MessageQueue#enqueMessage if sent directly or if sent using
+     * StateMachine#sendMessage the message will just be ignored.
+     *
+     * @param what is the assigned to Message.what.
+     * @param obj is assigned to Message.obj.
+     * @return  A Message object from the global pool
+     */
+    public final Message obtainMessage(int what, Object obj) {
+        return Message.obtain(mSmHandler, what, obj);
+    }
+
+    /**
+     * Get a message and set Message.target state machine handler,
+     * what, arg1 and arg2
+     *
+     * Note: The handler can be null if the state machine has quit,
+     * which means target will be null and may cause a AndroidRuntimeException
+     * in MessageQueue#enqueMessage if sent directly or if sent using
+     * StateMachine#sendMessage the message will just be ignored.
+     *
+     * @param what  is assigned to Message.what
+     * @param arg1  is assigned to Message.arg1
+     * @return  A Message object from the global pool
+     */
+    public final Message obtainMessage(int what, int arg1) {
+        // use this obtain so we don't match the obtain(h, what, Object) method
+        return Message.obtain(mSmHandler, what, arg1, 0);
+    }
+
+    /**
+     * Get a message and set Message.target state machine handler,
+     * what, arg1 and arg2
+     *
+     * Note: The handler can be null if the state machine has quit,
+     * which means target will be null and may cause a AndroidRuntimeException
+     * in MessageQueue#enqueMessage if sent directly or if sent using
+     * StateMachine#sendMessage the message will just be ignored.
+     *
+     * @param what  is assigned to Message.what
+     * @param arg1  is assigned to Message.arg1
+     * @param arg2  is assigned to Message.arg2
+     * @return  A Message object from the global pool
+     */
+    public final Message obtainMessage(int what, int arg1, int arg2) {
+        return Message.obtain(mSmHandler, what, arg1, arg2);
+    }
+
+    /**
+     * Get a message and set Message.target state machine handler,
+     * what, arg1, arg2 and obj
+     *
+     * Note: The handler can be null if the state machine has quit,
+     * which means target will be null and may cause a AndroidRuntimeException
+     * in MessageQueue#enqueMessage if sent directly or if sent using
+     * StateMachine#sendMessage the message will just be ignored.
+     *
+     * @param what  is assigned to Message.what
+     * @param arg1  is assigned to Message.arg1
+     * @param arg2  is assigned to Message.arg2
+     * @param obj is assigned to Message.obj
+     * @return  A Message object from the global pool
+     */
+    public final Message obtainMessage(int what, int arg1, int arg2, Object obj) {
+        return Message.obtain(mSmHandler, what, arg1, arg2, obj);
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessage(int what) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(obtainMessage(what));
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessage(int what, Object obj) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(obtainMessage(what, obj));
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessage(int what, int arg1) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(obtainMessage(what, arg1));
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessage(int what, int arg1, int arg2) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(obtainMessage(what, arg1, arg2));
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessage(int what, int arg1, int arg2, Object obj) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(obtainMessage(what, arg1, arg2, obj));
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessage(Message msg) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(msg);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessageDelayed(int what, long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(obtainMessage(what), delayMillis);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessageDelayed(int what, Object obj, long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessageDelayed(int what, int arg1, long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
+            long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(obtainMessage(what, arg1, arg2, obj), delayMillis);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public void sendMessageDelayed(Message msg, long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(msg, delayMillis);
+    }
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    protected final void sendMessageAtFrontOfQueue(int what) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what));
+    }
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
+    }
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    protected final void sendMessageAtFrontOfQueue(int what, int arg1) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1));
+    }
+
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2));
+    }
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2, obj));
+    }
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    protected final void sendMessageAtFrontOfQueue(Message msg) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(msg);
+    }
+
+    /**
+     * Removes a message from the message queue.
+     * Protected, may only be called by instances of StateMachine.
+     */
+    protected final void removeMessages(int what) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.removeMessages(what);
+    }
+
+    /**
+     * Removes a message from the deferred messages queue.
+     */
+    protected final void removeDeferredMessages(int what) {
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        Iterator<Message> iterator = smh.mDeferredMessages.iterator();
+        while (iterator.hasNext()) {
+            Message msg = iterator.next();
+            if (msg.what == what) iterator.remove();
+        }
+    }
+
+    /**
+     * Check if there are any pending messages with code 'what' in deferred messages queue.
+     */
+    protected final boolean hasDeferredMessages(int what) {
+        SmHandler smh = mSmHandler;
+        if (smh == null) return false;
+
+        Iterator<Message> iterator = smh.mDeferredMessages.iterator();
+        while (iterator.hasNext()) {
+            Message msg = iterator.next();
+            if (msg.what == what) return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Check if there are any pending posts of messages with code 'what' in
+     * the message queue. This does NOT check messages in deferred message queue.
+     */
+    protected final boolean hasMessages(int what) {
+        SmHandler smh = mSmHandler;
+        if (smh == null) return false;
+
+        return smh.hasMessages(what);
+    }
+
+    /**
+     * Validate that the message was sent by
+     * {@link StateMachine#quit} or {@link StateMachine#quitNow}.
+     */
+    protected final boolean isQuit(Message msg) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return msg.what == SM_QUIT_CMD;
+
+        return smh.isQuit(msg);
+    }
+
+    /**
+     * Quit the state machine after all currently queued up messages are processed.
+     */
+    public final void quit() {
+        // mSmHandler can be null if the state machine is already stopped.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.quit();
+    }
+
+    /**
+     * Quit the state machine immediately all currently queued messages will be discarded.
+     */
+    public final void quitNow() {
+        // mSmHandler can be null if the state machine is already stopped.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.quitNow();
+    }
+
+    /**
+     * @return if debugging is enabled
+     */
+    public boolean isDbg() {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return false;
+
+        return smh.isDbg();
+    }
+
+    /**
+     * Set debug enable/disabled.
+     *
+     * @param dbg is true to enable debugging.
+     */
+    public void setDbg(boolean dbg) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.setDbg(dbg);
+    }
+
+    /**
+     * Start the state machine.
+     */
+    public void start() {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        /** Send the complete construction message */
+        smh.completeConstruction();
+    }
+
+    /**
+     * Dump the current state.
+     *
+     * @param fd
+     * @param pw
+     * @param args
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(getName() + ":");
+        pw.println(" total records=" + getLogRecCount());
+        for (int i = 0; i < getLogRecSize(); i++) {
+            pw.println(" rec[" + i + "]: " + getLogRec(i).toString());
+            pw.flush();
+        }
+        pw.println("curState=" + getCurrentState().getName());
+    }
+
+    @Override
+    public String toString() {
+        String name = "(null)";
+        String state = "(null)";
+        try {
+            name = mName.toString();
+            state = mSmHandler.getCurrentState().getName().toString();
+        } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
+            // Will use default(s) initialized above.
+        }
+        return "name=" + name + " state=" + state;
+    }
+
+    /**
+     * Log with debug and add to the LogRecords.
+     *
+     * @param s is string log
+     */
+    protected void logAndAddLogRec(String s) {
+        addLogRec(s);
+        log(s);
+    }
+
+    /**
+     * Log with debug
+     *
+     * @param s is string log
+     */
+    protected void log(String s) {
+        Log.d(mName, s);
+    }
+
+    /**
+     * Log with debug attribute
+     *
+     * @param s is string log
+     */
+    protected void logd(String s) {
+        Log.d(mName, s);
+    }
+
+    /**
+     * Log with verbose attribute
+     *
+     * @param s is string log
+     */
+    protected void logv(String s) {
+        Log.v(mName, s);
+    }
+
+    /**
+     * Log with info attribute
+     *
+     * @param s is string log
+     */
+    protected void logi(String s) {
+        Log.i(mName, s);
+    }
+
+    /**
+     * Log with warning attribute
+     *
+     * @param s is string log
+     */
+    protected void logw(String s) {
+        Log.w(mName, s);
+    }
+
+    /**
+     * Log with error attribute
+     *
+     * @param s is string log
+     */
+    protected void loge(String s) {
+        Log.e(mName, s);
+    }
+
+    /**
+     * Log with error attribute
+     *
+     * @param s is string log
+     * @param e is a Throwable which logs additional information.
+     */
+    protected void loge(String s, Throwable e) {
+        Log.e(mName, s, e);
+    }
+}
diff --git a/src/com/android/bluetooth/util/DevicePolicyUtils.java b/src/com/android/bluetooth/util/DevicePolicyUtils.java
index 5f11a29..0af4cf7 100644
--- a/src/com/android/bluetooth/util/DevicePolicyUtils.java
+++ b/src/com/android/bluetooth/util/DevicePolicyUtils.java
@@ -37,7 +37,7 @@
 
         // Check each user.
         for (UserInfo ui : userInfoList) {
-            if (!ui.isManagedProfile()) {
+            if (!userManager.isManagedProfile(ui.id)) {
                 continue; // Not a managed user.
             }
             return dpm.getBluetoothContactSharingDisabled(new UserHandle(ui.id));
diff --git a/src/com/android/bluetooth/util/GsmAlphabet.java b/src/com/android/bluetooth/util/GsmAlphabet.java
new file mode 100644
index 0000000..e723208
--- /dev/null
+++ b/src/com/android/bluetooth/util/GsmAlphabet.java
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.util;
+
+import android.content.res.Resources;
+import android.telephony.Rlog;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+
+/**
+ * This class implements the character set mapping between
+ * the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1
+ * and UTF-16
+ *
+ * {@hide}
+ */
+public class GsmAlphabet {
+    private static final String TAG = "GSM";
+
+    /**
+     * This escapes extended characters, and when present indicates that the
+     * following character should be looked up in the "extended" table.
+     *
+     * gsmToChar(GSM_EXTENDED_ESCAPE) returns 0xffff
+     */
+    public static final byte GSM_EXTENDED_ESCAPE = 0x1B;
+
+    /**
+     * User data header requires one octet for length. Count as one septet, because
+     * all combinations of header elements below will have at least one free bit
+     * when padding to the nearest septet boundary.
+     */
+    public static final int UDH_SEPTET_COST_LENGTH = 1;
+
+    /**
+     * Using a non-default language locking shift table OR single shift table
+     * requires a user data header of 3 octets, or 4 septets, plus UDH length.
+     */
+    public static final int UDH_SEPTET_COST_ONE_SHIFT_TABLE = 4;
+
+    /**
+     * Using a non-default language locking shift table AND single shift table
+     * requires a user data header of 6 octets, or 7 septets, plus UDH length.
+     */
+    public static final int UDH_SEPTET_COST_TWO_SHIFT_TABLES = 7;
+
+    /**
+     * Multi-part messages require a user data header of 5 octets, or 6 septets,
+     * plus UDH length.
+     */
+    public static final int UDH_SEPTET_COST_CONCATENATED_MESSAGE = 6;
+
+    /**
+     * For a specific text string, this object describes protocol
+     * properties of encoding it for transmission as message user
+     * data.
+     */
+    public static class TextEncodingDetails {
+
+        public TextEncodingDetails() {
+        }
+
+        /**
+         * The number of SMS's required to encode the text.
+         */
+        public int msgCount;
+
+        /**
+         * The number of code units consumed so far, where code units
+         * are basically characters in the encoding -- for example,
+         * septets for the standard ASCII and GSM encodings, and 16
+         * bits for Unicode.
+         */
+        public int codeUnitCount;
+
+        /**
+         * How many code units are still available without spilling
+         * into an additional message.
+         */
+        public int codeUnitsRemaining;
+
+        /**
+         * The encoding code unit size (specified using
+         * android.telephony.SmsMessage ENCODING_*).
+         */
+        public int codeUnitSize;
+
+        /**
+         * The GSM national language table to use, or 0 for the default 7-bit alphabet.
+         */
+        public int languageTable;
+
+        /**
+         * The GSM national language shift table to use, or 0 for the default 7-bit extension table.
+         */
+        public int languageShiftTable;
+
+        @Override
+        public String toString() {
+            return "TextEncodingDetails "
+                + "{ msgCount=" + msgCount
+                + ", codeUnitCount=" + codeUnitCount
+                + ", codeUnitsRemaining=" + codeUnitsRemaining
+                + ", codeUnitSize=" + codeUnitSize
+                + ", languageTable=" + languageTable
+                + ", languageShiftTable=" + languageShiftTable
+                + " }";
+        }
+    }
+
+    /**
+     * Convert a GSM alphabet 7 bit packed string (SMS string) into a
+     * {@link java.lang.String}.
+     *
+     * See TS 23.038 6.1.2.1 for SMS Character Packing
+     *
+     * @param pdu the raw data from the pdu
+     * @param offset the byte offset of
+     * @param lengthSeptets string length in septets, not bytes
+     * @param numPaddingBits the number of padding bits before the start of the
+     *  string in the first byte
+     * @param languageTable the 7 bit language table, or 0 for the default GSM alphabet
+     * @param shiftTable the 7 bit single shift language table, or 0 for the default
+     *     GSM extension table
+     * @return String representation or null on decoding exception
+     */
+    public static String gsm7BitPackedToString(byte[] pdu, int offset, int lengthSeptets,
+                                               int numPaddingBits, int languageTable,
+                                               int shiftTable) {
+        StringBuilder ret = new StringBuilder(lengthSeptets);
+
+        if (languageTable < 0 || languageTable > sLanguageTables.length) {
+            Rlog.w(TAG, "unknown language table " + languageTable + ", using default");
+            languageTable = 0;
+        }
+        if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) {
+            Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default");
+            shiftTable = 0;
+        }
+
+        try {
+            boolean prevCharWasEscape = false;
+            String languageTableToChar = sLanguageTables[languageTable];
+            String shiftTableToChar = sLanguageShiftTables[shiftTable];
+
+            if (languageTableToChar.isEmpty()) {
+                Rlog.w(TAG, "no language table for code " + languageTable + ", using default");
+                languageTableToChar = sLanguageTables[0];
+            }
+            if (shiftTableToChar.isEmpty()) {
+                Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default");
+                shiftTableToChar = sLanguageShiftTables[0];
+            }
+
+            for (int i = 0; i < lengthSeptets; i++) {
+                int bitOffset = (7 * i) + numPaddingBits;
+
+                int byteOffset = bitOffset / 8;
+                int shift = bitOffset % 8;
+                int gsmVal;
+
+                gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
+
+                // if it crosses a byte boundary
+                if (shift > 1) {
+                    // set msb bits to 0
+                    gsmVal &= 0x7f >> (shift - 1);
+
+                    gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
+                }
+
+                if (prevCharWasEscape) {
+                    if (gsmVal == GSM_EXTENDED_ESCAPE) {
+                        ret.append(' ');    // display ' ' for reserved double escape sequence
+                    } else {
+                        char c = shiftTableToChar.charAt(gsmVal);
+                        if (c == ' ') {
+                            ret.append(languageTableToChar.charAt(gsmVal));
+                        } else {
+                            ret.append(c);
+                        }
+                    }
+                    prevCharWasEscape = false;
+                } else if (gsmVal == GSM_EXTENDED_ESCAPE) {
+                    prevCharWasEscape = true;
+                } else {
+                    ret.append(languageTableToChar.charAt(gsmVal));
+                }
+            }
+        } catch (RuntimeException ex) {
+            Rlog.e(TAG, "Error GSM 7 bit packed: ", ex);
+            return null;
+        }
+
+        return ret.toString();
+    }
+
+    /**
+     * Convert a string into an 8-bit unpacked GSM alphabet byte array.
+     * Always uses GSM default 7-bit alphabet and extension table.
+     * @param s the string to encode
+     * @return the 8-bit GSM encoded byte array for the string
+     */
+    public static byte[] stringToGsm8BitPacked(String s) {
+        byte[] ret;
+
+        int septets = countGsmSeptetsUsingTables(s, true, 0, 0);
+
+        // Enough for all the septets and the length byte prefix
+        ret = new byte[septets];
+
+        stringToGsm8BitUnpackedField(s, ret, 0, ret.length);
+
+        return ret;
+    }
+
+    /**
+     * Write a String into a GSM 8-bit unpacked field of
+     * Field is padded with 0xff's, string is truncated if necessary
+     *
+     * @param s the string to encode
+     * @param dest the destination byte array
+     * @param offset the starting offset for the encoded string
+     * @param length the maximum number of bytes to write
+     */
+    public static void stringToGsm8BitUnpackedField(String s, byte[] dest, int offset,
+                                                    int length) {
+        int outByteIndex = offset;
+        SparseIntArray charToLanguageTable = sCharsToGsmTables[0];
+        SparseIntArray charToShiftTable = sCharsToShiftTables[0];
+
+        // Septets are stored in byte-aligned octets
+        for (int i = 0, sz = s.length(); i < sz && (outByteIndex - offset) < length; i++) {
+            char c = s.charAt(i);
+            int v = charToLanguageTable.get(c, -1);
+            if (v == -1) {
+                v = charToShiftTable.get(c, -1);
+                if (v == -1) {
+                    v = charToLanguageTable.get(' ', ' ');  // fall back to ASCII space
+                } else {
+                    // make sure we can fit an escaped char
+                    if (!(outByteIndex + 1 - offset < length)) {
+                        break;
+                    }
+
+                    dest[outByteIndex++] = GSM_EXTENDED_ESCAPE;
+                }
+            }
+
+            dest[outByteIndex++] = (byte) v;
+        }
+
+        // pad with 0xff's
+        while ((outByteIndex - offset) < length) {
+            dest[outByteIndex++] = (byte) 0xff;
+        }
+    }
+
+    /**
+     * Returns the count of 7-bit GSM alphabet characters needed
+     * to represent this string, using the specified 7-bit language table
+     * and extension table (0 for GSM default tables).
+     * @param s the Unicode string that will be encoded
+     * @param use7bitOnly allow using space in place of unencodable character if true,
+     *     otherwise, return -1 if any characters are unencodable
+     * @param languageTable the 7 bit language table, or 0 for the default GSM alphabet
+     * @param languageShiftTable the 7 bit single shift language table, or 0 for the default
+     *     GSM extension table
+     * @return the septet count for s using the specified language tables, or -1 if any
+     *     characters are unencodable and use7bitOnly is false
+     */
+    public static int countGsmSeptetsUsingTables(CharSequence s, boolean use7bitOnly,
+                                                 int languageTable, int languageShiftTable) {
+        int count = 0;
+        int sz = s.length();
+        SparseIntArray charToLanguageTable = sCharsToGsmTables[languageTable];
+        SparseIntArray charToShiftTable = sCharsToShiftTables[languageShiftTable];
+        for (int i = 0; i < sz; i++) {
+            char c = s.charAt(i);
+            if (c == GSM_EXTENDED_ESCAPE) {
+                Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
+                continue;
+            }
+            if (charToLanguageTable.get(c, -1) != -1) {
+                count++;
+            } else if (charToShiftTable.get(c, -1) != -1) {
+                count += 2; // escape + shift table index
+            } else if (use7bitOnly) {
+                count++;    // encode as space
+            } else {
+                return -1;  // caller must check for this case
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Enable country-specific language tables from MCC-specific overlays.
+     * @context the context to use to get the TelephonyManager
+     */
+    private static void enableCountrySpecificEncodings() {
+        Resources r = Resources.getSystem();
+        // See comments in frameworks/base/core/res/res/values/config.xml for allowed values
+        sEnabledSingleShiftTables = r.getIntArray(R.array.config_sms_enabled_single_shift_tables);
+        sEnabledLockingShiftTables = r.getIntArray(R.array.config_sms_enabled_locking_shift_tables);
+
+        if (sEnabledSingleShiftTables.length > 0) {
+            sHighestEnabledSingleShiftCode =
+                sEnabledSingleShiftTables[sEnabledSingleShiftTables.length - 1];
+        } else {
+            sHighestEnabledSingleShiftCode = 0;
+        }
+    }
+
+    /** Reverse mapping from Unicode characters to indexes into language tables. */
+    private static final SparseIntArray[] sCharsToGsmTables;
+
+    /** Reverse mapping from Unicode characters to indexes into language shift tables. */
+    private static final SparseIntArray[] sCharsToShiftTables;
+
+    /** OEM configured list of enabled national language single shift tables for encoding. */
+    private static int[] sEnabledSingleShiftTables;
+
+    /** OEM configured list of enabled national language locking shift tables for encoding. */
+    private static int[] sEnabledLockingShiftTables;
+
+    /** Highest language code to include in array of single shift counters. */
+    private static int sHighestEnabledSingleShiftCode;
+
+    /**
+     * GSM default 7 bit alphabet plus national language locking shift character tables.
+     * Comment lines above strings indicate the lower four bits of the table position.
+     */
+    private static final String[] sLanguageTables = {
+        /* 3GPP TS 23.038 V9.1.1 section 6.2.1 - GSM 7 bit Default Alphabet
+         01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....0.....1 */
+        "@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\n\u00d8\u00f8\r\u00c5\u00e5\u0394_"
+            // 2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....
+            + "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\uffff\u00c6\u00e6\u00df"
+            // F.....012.34.....56789ABCDEF0123456789ABCDEF0.....123456789ABCDEF0123456789A
+            + "\u00c9 !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+            // B.....C.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....
+            + "\u00c4\u00d6\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1"
+            // E.....F.....
+            + "\u00fc\u00e0",
+
+        /* A.3.1 Turkish National Language Locking Shift Table
+         01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....0.....1 */
+        "@\u00a3$\u00a5\u20ac\u00e9\u00f9\u0131\u00f2\u00c7\n\u011e\u011f\r\u00c5\u00e5\u0394_"
+            // 2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....
+            + "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\uffff\u015e\u015f\u00df"
+            // F.....012.34.....56789ABCDEF0123456789ABCDEF0.....123456789ABCDEF0123456789A
+            + "\u00c9 !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u0130ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+            // B.....C.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....
+            + "\u00c4\u00d6\u00d1\u00dc\u00a7\u00e7abcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1"
+            // E.....F.....
+            + "\u00fc\u00e0",
+
+        /* A.3.2 Void (no locking shift table for Spanish) */
+        "",
+
+        /* A.3.3 Portuguese National Language Locking Shift Table
+         01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....0.....1 */
+        "@\u00a3$\u00a5\u00ea\u00e9\u00fa\u00ed\u00f3\u00e7\n\u00d4\u00f4\r\u00c1\u00e1\u0394_"
+            // 2.....3.....4.....5.....67.8.....9.....AB.....C.....D.....E.....F.....012.34.....
+            + "\u00aa\u00c7\u00c0\u221e^\\\u20ac\u00d3|\uffff\u00c2\u00e2\u00ca\u00c9 !\"#\u00ba"
+            // 56789ABCDEF0123456789ABCDEF0.....123456789ABCDEF0123456789AB.....C.....D.....E.....
+            + "%&'()*+,-./0123456789:;<=>?\u00cdABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c3\u00d5\u00da\u00dc"
+            // F.....0123456789ABCDEF0123456789AB.....C.....DE.....F.....
+            + "\u00a7~abcdefghijklmnopqrstuvwxyz\u00e3\u00f5`\u00fc\u00e0",
+
+        /* A.3.4 Bengali National Language Locking Shift Table
+         0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.EF.....0..... */
+        "\u0981\u0982\u0983\u0985\u0986\u0987\u0988\u0989\u098a\u098b\n\u098c \r \u098f\u0990"
+            // 123.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+            + "  \u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\uffff\u099b\u099c\u099d\u099e"
+            // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABC
+            + " !\u099f\u09a0\u09a1\u09a2\u09a3\u09a4)(\u09a5\u09a6,\u09a7.\u09a80123456789:; "
+            // D.....E.....F0.....1.....2.....3.....4.....56.....789A.....B.....C.....D.....
+            + "\u09aa\u09ab?\u09ac\u09ad\u09ae\u09af\u09b0 \u09b2   \u09b6\u09b7\u09b8\u09b9"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....789.....A.....BCD.....E.....
+            + "\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4  \u09c7\u09c8  \u09cb\u09cc"
+            // F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....F.....
+            + "\u09cd\u09ceabcdefghijklmnopqrstuvwxyz\u09d7\u09dc\u09dd\u09f0\u09f1",
+
+        /* A.3.5 Gujarati National Language Locking Shift Table
+         0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.EF.....0.....*/
+        "\u0a81\u0a82\u0a83\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\n\u0a8c\u0a8d\r \u0a8f\u0a90"
+            // 1.....23.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....
+            + "\u0a91 \u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\uffff\u0a9b\u0a9c\u0a9d"
+            // F.....012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789AB
+            + "\u0a9e !\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4)(\u0aa5\u0aa6,\u0aa7.\u0aa80123456789:;"
+            // CD.....E.....F0.....1.....2.....3.....4.....56.....7.....89.....A.....B.....C.....
+            + " \u0aaa\u0aab?\u0aac\u0aad\u0aae\u0aaf\u0ab0 \u0ab2\u0ab3 \u0ab5\u0ab6\u0ab7\u0ab8"
+            // D.....E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....
+            + "\u0ab9\u0abc\u0abd\u0abe\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5 \u0ac7\u0ac8"
+            // B.....CD.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....
+            + "\u0ac9 \u0acb\u0acc\u0acd\u0ad0abcdefghijklmnopqrstuvwxyz\u0ae0\u0ae1\u0ae2\u0ae3"
+            // F.....
+            + "\u0af1",
+
+        /* A.3.6 Hindi National Language Locking Shift Table
+         0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....*/
+        "\u0901\u0902\u0903\u0905\u0906\u0907\u0908\u0909\u090a\u090b\n\u090c\u090d\r\u090e\u090f"
+            // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....
+            + "\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\uffff\u091b\u091c"
+            // E.....F.....012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....012345
+            + "\u091d\u091e !\u091f\u0920\u0921\u0922\u0923\u0924)(\u0925\u0926,\u0927.\u0928012345"
+            // 6789ABC.....D.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....8.....
+            + "6789:;\u0929\u092a\u092b?\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934"
+            // 9.....A.....B.....C.....D.....E.....F.....0.....1.....2.....3.....4.....5.....6.....
+            + "\u0935\u0936\u0937\u0938\u0939\u093c\u093d\u093e\u093f\u0940\u0941\u0942\u0943\u0944"
+            // 7.....8.....9.....A.....B.....C.....D.....E.....F.....0.....123456789ABCDEF012345678
+            + "\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u0950abcdefghijklmnopqrstuvwx"
+            // 9AB.....C.....D.....E.....F.....
+            + "yz\u0972\u097b\u097c\u097e\u097f",
+
+        /* A.3.7 Kannada National Language Locking Shift Table
+           NOTE: TS 23.038 V9.1.1 shows code 0x24 as \u0caa, corrected to \u0ca1 (typo)
+         01.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F.....0.....1 */
+        " \u0c82\u0c83\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\n\u0c8c \r\u0c8e\u0c8f\u0c90 "
+            // 2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+            + "\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\uffff\u0c9b\u0c9c\u0c9d\u0c9e"
+            // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABC
+            + " !\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4)(\u0ca5\u0ca6,\u0ca7.\u0ca80123456789:; "
+            // D.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....
+            + "\u0caa\u0cab?\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3 \u0cb5\u0cb6\u0cb7"
+            // C.....D.....E.....F.....0.....1.....2.....3.....4.....5.....6.....78.....9.....
+            + "\u0cb8\u0cb9\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4 \u0cc6\u0cc7"
+            // A.....BC.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....
+            + "\u0cc8 \u0cca\u0ccb\u0ccc\u0ccd\u0cd5abcdefghijklmnopqrstuvwxyz\u0cd6\u0ce0\u0ce1"
+            // E.....F.....
+            + "\u0ce2\u0ce3",
+
+        /* A.3.8 Malayalam National Language Locking Shift Table
+         01.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F.....0.....1 */
+        " \u0d02\u0d03\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\n\u0d0c \r\u0d0e\u0d0f\u0d10 "
+            // 2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+            + "\u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\uffff\u0d1b\u0d1c\u0d1d\u0d1e"
+            // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABC
+            + " !\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24)(\u0d25\u0d26,\u0d27.\u0d280123456789:; "
+            // D.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....
+            + "\u0d2a\u0d2b?\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36"
+            // B.....C.....D.....EF.....0.....1.....2.....3.....4.....5.....6.....78.....9.....
+            + "\u0d37\u0d38\u0d39 \u0d3d\u0d3e\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44 \u0d46\u0d47"
+            // A.....BC.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....
+            + "\u0d48 \u0d4a\u0d4b\u0d4c\u0d4d\u0d57abcdefghijklmnopqrstuvwxyz\u0d60\u0d61\u0d62"
+            // E.....F.....
+            + "\u0d63\u0d79",
+
+        /* A.3.9 Oriya National Language Locking Shift Table
+         0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.EF.....0.....12 */
+        "\u0b01\u0b02\u0b03\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\n\u0b0c \r \u0b0f\u0b10  "
+            // 3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....01
+            + "\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\uffff\u0b1b\u0b1c\u0b1d\u0b1e !"
+            // 2.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABCD.....
+            + "\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24)(\u0b25\u0b26,\u0b27.\u0b280123456789:; \u0b2a"
+            // E.....F0.....1.....2.....3.....4.....56.....7.....89.....A.....B.....C.....D.....
+            + "\u0b2b?\u0b2c\u0b2d\u0b2e\u0b2f\u0b30 \u0b32\u0b33 \u0b35\u0b36\u0b37\u0b38\u0b39"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....789.....A.....BCD.....E.....
+            + "\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44  \u0b47\u0b48  \u0b4b\u0b4c"
+            // F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....F.....
+            + "\u0b4d\u0b56abcdefghijklmnopqrstuvwxyz\u0b57\u0b60\u0b61\u0b62\u0b63",
+
+        /* A.3.10 Punjabi National Language Locking Shift Table
+         0.....1.....2.....3.....4.....5.....6.....7.....8.....9A.BCD.EF.....0.....123.....4.....*/
+        "\u0a01\u0a02\u0a03\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a \n  \r \u0a0f\u0a10  \u0a13\u0a14"
+            // 5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....012.....3.....
+            + "\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\uffff\u0a1b\u0a1c\u0a1d\u0a1e !\u0a1f\u0a20"
+            // 4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABCD.....E.....F0.....
+            + "\u0a21\u0a22\u0a23\u0a24)(\u0a25\u0a26,\u0a27.\u0a280123456789:; \u0a2a\u0a2b?\u0a2c"
+            // 1.....2.....3.....4.....56.....7.....89.....A.....BC.....D.....E.....F0.....1.....
+            + "\u0a2d\u0a2e\u0a2f\u0a30 \u0a32\u0a33 \u0a35\u0a36 \u0a38\u0a39\u0a3c \u0a3e\u0a3f"
+            // 2.....3.....4.....56789.....A.....BCD.....E.....F.....0.....123456789ABCDEF012345678
+            + "\u0a40\u0a41\u0a42    \u0a47\u0a48  \u0a4b\u0a4c\u0a4d\u0a51abcdefghijklmnopqrstuvwx"
+            // 9AB.....C.....D.....E.....F.....
+            + "yz\u0a70\u0a71\u0a72\u0a73\u0a74",
+
+        /* A.3.11 Tamil National Language Locking Shift Table
+         01.....2.....3.....4.....5.....6.....7.....8.....9A.BCD.E.....F.....0.....12.....3..... */
+        " \u0b82\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a \n  \r\u0b8e\u0b8f\u0b90 \u0b92\u0b93"
+            // 4.....5.....6789.....A.....B.....CD.....EF.....012.....3456.....7.....89ABCDEF.....
+            + "\u0b94\u0b95   \u0b99\u0b9a\uffff \u0b9c \u0b9e !\u0b9f   \u0ba3\u0ba4)(  , .\u0ba8"
+            // 0123456789ABC.....D.....EF012.....3.....4.....5.....6.....7.....8.....9.....A.....
+            + "0123456789:;\u0ba9\u0baa ?  \u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6"
+            // B.....C.....D.....EF0.....1.....2.....3.....4.....5678.....9.....A.....BC.....D.....
+            + "\u0bb7\u0bb8\u0bb9  \u0bbe\u0bbf\u0bc0\u0bc1\u0bc2   \u0bc6\u0bc7\u0bc8 \u0bca\u0bcb"
+            // E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....F.....
+            + "\u0bcc\u0bcd\u0bd0abcdefghijklmnopqrstuvwxyz\u0bd7\u0bf0\u0bf1\u0bf2\u0bf9",
+
+        /* A.3.12 Telugu National Language Locking Shift Table
+         0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F.....0.....*/
+        "\u0c01\u0c02\u0c03\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\n\u0c0c \r\u0c0e\u0c0f\u0c10"
+            // 12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....
+            + " \u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\uffff\u0c1b\u0c1c\u0c1d"
+            // F.....012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789AB
+            + "\u0c1e !\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24)(\u0c25\u0c26,\u0c27.\u0c280123456789:;"
+            // CD.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....
+            + " \u0c2a\u0c2b?\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33 \u0c35\u0c36\u0c37"
+            // C.....D.....EF.....0.....1.....2.....3.....4.....5.....6.....78.....9.....A.....B
+            + "\u0c38\u0c39 \u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44 \u0c46\u0c47\u0c48 "
+            // C.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....
+            + "\u0c4a\u0c4b\u0c4c\u0c4d\u0c55abcdefghijklmnopqrstuvwxyz\u0c56\u0c60\u0c61\u0c62"
+            // F.....
+            + "\u0c63",
+
+        /* A.3.13 Urdu National Language Locking Shift Table
+         0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....*/
+        "\u0627\u0622\u0628\u067b\u0680\u067e\u06a6\u062a\u06c2\u067f\n\u0679\u067d\r\u067a\u067c"
+            // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....
+            + "\u062b\u062c\u0681\u0684\u0683\u0685\u0686\u0687\u062d\u062e\u062f\uffff\u068c\u0688"
+            // E.....F.....012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....012345
+            + "\u0689\u068a !\u068f\u068d\u0630\u0631\u0691\u0693)(\u0699\u0632,\u0696.\u0698012345"
+            // 6789ABC.....D.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....8.....
+            + "6789:;\u069a\u0633\u0634?\u0635\u0636\u0637\u0638\u0639\u0641\u0642\u06a9\u06aa"
+            // 9.....A.....B.....C.....D.....E.....F.....0.....1.....2.....3.....4.....5.....6.....
+            + "\u06ab\u06af\u06b3\u06b1\u0644\u0645\u0646\u06ba\u06bb\u06bc\u0648\u06c4\u06d5\u06c1"
+            // 7.....8.....9.....A.....B.....C.....D.....E.....F.....0.....123456789ABCDEF012345678
+            + "\u06be\u0621\u06cc\u06d0\u06d2\u064d\u0650\u064f\u0657\u0654abcdefghijklmnopqrstuvwx"
+            // 9AB.....C.....D.....E.....F.....
+            + "yz\u0655\u0651\u0653\u0656\u0670"
+    };
+
+    /**
+     * GSM default extension table plus national language single shift character tables.
+     */
+    private static final String[] sLanguageShiftTables = new String[]{
+        /* 6.2.1.1 GSM 7 bit Default Alphabet Extension Table
+         0123456789A.....BCDEF0123456789ABCDEF0123456789ABCDEF.0123456789ABCDEF0123456789ABCDEF */
+        "          \u000c         ^                   {}     \\            [~] |               "
+            // 0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+            + "                     \u20ac                          ",
+
+        /* A.2.1 Turkish National Language Single Shift Table
+         0123456789A.....BCDEF0123456789ABCDEF0123456789ABCDEF.0123456789ABCDEF01234567.....8 */
+        "          \u000c         ^                   {}     \\            [~] |      \u011e "
+            // 9.....ABCDEF0123.....456789ABCDEF0123.....45.....67.....89.....ABCDEF0123.....
+            + "\u0130         \u015e               \u00e7 \u20ac \u011f \u0131         \u015f"
+            // 456789ABCDEF
+            + "            ",
+
+        /* A.2.2 Spanish National Language Single Shift Table
+         0123456789.....A.....BCDEF0123456789ABCDEF0123456789ABCDEF.0123456789ABCDEF01.....23 */
+        "         \u00e7\u000c         ^                   {}     \\            [~] |\u00c1  "
+            // 456789.....ABCDEF.....012345.....6789ABCDEF01.....2345.....6789.....ABCDEF.....012
+            + "     \u00cd     \u00d3     \u00da           \u00e1   \u20ac   \u00ed     \u00f3   "
+            // 345.....6789ABCDEF
+            + "  \u00fa          ",
+
+        /* A.2.3 Portuguese National Language Single Shift Table
+         012345.....6789.....A.....B.....C.....DE.....F.....012.....3.....45.....6.....7.....8....*/
+        "     \u00ea   \u00e7\u000c\u00d4\u00f4 \u00c1\u00e1  \u03a6\u0393^\u03a9\u03a0\u03a8\u03a3"
+            // 9.....ABCDEF.....0123456789ABCDEF.0123456789ABCDEF01.....23456789.....ABCDE
+            + "\u0398     \u00ca        {}     \\            [~] |\u00c0       \u00cd     "
+            // F.....012345.....6789AB.....C.....DEF01.....2345.....6789.....ABCDEF.....01234
+            + "\u00d3     \u00da     \u00c3\u00d5    \u00c2   \u20ac   \u00ed     \u00f3     "
+            // 5.....6789AB.....C.....DEF.....
+            + "\u00fa     \u00e3\u00f5  \u00e2",
+
+        /* A.2.4 Bengali National Language Single Shift Table
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u09e6\u09e7 \u09e8\u09e9"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+            + "\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09df\u09e0\u09e1\u09e2{}\u09e3\u09f2\u09f3"
+            // D.....E.....F.0.....1.....2.....3.....4.....56789ABCDEF0123456789ABCDEF
+            + "\u09f4\u09f5\\\u09f6\u09f7\u09f8\u09f9\u09fa       [~] |ABCDEFGHIJKLMNO"
+            // 0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+            + "PQRSTUVWXYZ          \u20ac                          ",
+
+        /* A.2.5 Gujarati National Language Single Shift Table
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0ae6\u0ae7"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6789ABCDEF.0123456789ABCDEF
+            + "\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef  {}     \\            [~] "
+            // 0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+            + "|ABCDEFGHIJKLMNOPQRSTUVWXYZ          \u20ac                          ",
+
+        /* A.2.6 Hindi National Language Single Shift Table
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0966\u0967"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+            + "\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u0951\u0952{}\u0953\u0954\u0958"
+            // D.....E.....F.0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....
+            + "\u0959\u095a\\\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0970\u0971"
+            // BCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+            + " [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ          \u20ac                          ",
+
+        /* A.2.7 Kannada National Language Single Shift Table
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0ce6\u0ce7"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....BCDEF.01234567
+            + "\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0cde\u0cf1{}\u0cf2    \\        "
+            // 89ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+            + "    [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ          \u20ac                          ",
+
+        /* A.2.8 Malayalam National Language Single Shift Table
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0d66\u0d67"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+            + "\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71{}\u0d72\u0d73\u0d74"
+            // D.....E.....F.0.....1.....2.....3.....4.....56789ABCDEF0123456789ABCDEF0123456789A
+            + "\u0d75\u0d7a\\\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f       [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+            // BCDEF012345.....6789ABCDEF0123456789ABCDEF
+            + "          \u20ac                          ",
+
+        /* A.2.9 Oriya National Language Single Shift Table
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0b66\u0b67"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....DE
+            + "\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b5c\u0b5d{}\u0b5f\u0b70\u0b71  "
+            // F.0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789A
+            + "\\            [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ          \u20ac                     "
+            // BCDEF
+            + "     ",
+
+        /* A.2.10 Punjabi National Language Single Shift Table
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0a66\u0a67"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+            + "\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0a59\u0a5a{}\u0a5b\u0a5c\u0a5e"
+            // D.....EF.0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF01
+            + "\u0a75 \\            [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ          \u20ac            "
+            // 23456789ABCDEF
+            + "              ",
+
+        /* A.2.11 Tamil National Language Single Shift Table
+           NOTE: TS 23.038 V9.1.1 shows code 0x24 as \u0bef, corrected to \u0bee (typo)
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0be6\u0be7"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+            + "\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf3\u0bf4{}\u0bf5\u0bf6\u0bf7"
+            // D.....E.....F.0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABC
+            + "\u0bf8\u0bfa\\            [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ          \u20ac       "
+            // DEF0123456789ABCDEF
+            + "                   ",
+
+        /* A.2.12 Telugu National Language Single Shift Table
+           NOTE: TS 23.038 V9.1.1 shows code 0x22-0x23 as \u06cc\u06cd, corrected to \u0c6c\u0c6d
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789ABC.....D.....E.....F..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*   \u0c66\u0c67\u0c68\u0c69"
+            // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F.
+            + "\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c58\u0c59{}\u0c78\u0c79\u0c7a\u0c7b\u0c7c\\"
+            // 0.....1.....2.....3456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCD
+            + "\u0c7d\u0c7e\u0c7f         [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ          \u20ac        "
+            // EF0123456789ABCDEF
+            + "                  ",
+
+        /* A.2.13 Urdu National Language Single Shift Table
+         01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+        "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0600\u0601 \u06f0\u06f1"
+            // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+            + "\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u060c\u060d{}\u060e\u060f\u0610"
+            // D.....E.....F.0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....
+            + "\u0611\u0612\\\u0613\u0614\u061b\u061f\u0640\u0652\u0658\u066b\u066c\u0672\u0673"
+            // B.....CDEF.....0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+            + "\u06cd[~]\u06d4|ABCDEFGHIJKLMNOPQRSTUVWXYZ          \u20ac                          "
+    };
+
+    static {
+        enableCountrySpecificEncodings();
+        int numTables = sLanguageTables.length;
+        int numShiftTables = sLanguageShiftTables.length;
+        if (numTables != numShiftTables) {
+            Rlog.e(TAG, "Error: language tables array length " + numTables
+                    + " != shift tables array length " + numShiftTables);
+        }
+
+        sCharsToGsmTables = new SparseIntArray[numTables];
+        for (int i = 0; i < numTables; i++) {
+            String table = sLanguageTables[i];
+
+            int tableLen = table.length();
+            if (tableLen != 0 && tableLen != 128) {
+                Rlog.e(TAG, "Error: language tables index " + i
+                        + " length " + tableLen + " (expected 128 or 0)");
+            }
+
+            SparseIntArray charToGsmTable = new SparseIntArray(tableLen);
+            sCharsToGsmTables[i] = charToGsmTable;
+            for (int j = 0; j < tableLen; j++) {
+                char c = table.charAt(j);
+                charToGsmTable.put(c, j);
+            }
+        }
+
+        sCharsToShiftTables = new SparseIntArray[numShiftTables];
+        for (int i = 0; i < numShiftTables; i++) {
+            String shiftTable = sLanguageShiftTables[i];
+
+            int shiftTableLen = shiftTable.length();
+            if (shiftTableLen != 0 && shiftTableLen != 128) {
+                Rlog.e(TAG, "Error: language shift tables index " + i
+                        + " length " + shiftTableLen + " (expected 128 or 0)");
+            }
+
+            SparseIntArray charToShiftTable = new SparseIntArray(shiftTableLen);
+            sCharsToShiftTables[i] = charToShiftTable;
+            for (int j = 0; j < shiftTableLen; j++) {
+                char c = shiftTable.charAt(j);
+                if (c != ' ') {
+                    charToShiftTable.put(c, j);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
new file mode 100644
index 0000000..68b70c2
--- /dev/null
+++ b/tests/unit/Android.bp
@@ -0,0 +1,37 @@
+android_test {
+    name: "BluetoothInstrumentationTests",
+
+    // We only want this apk build for tests.
+    certificate: "platform",
+
+    libs: [
+        "javax.obex",
+        "android.test.runner",
+        "telephony-common",
+        "libprotobuf-java-micro",
+        "android.test.base",
+        "android.test.mock",
+    ],
+
+    static_libs: [
+        "androidx.test.rules",
+        "mockito-target",
+        "androidx.test.espresso.intents",
+        "gson-prebuilt-jar",
+        "androidx.room_room-migration",
+        "androidx.room_room-runtime",
+        "androidx.room_room-testing",
+    ],
+
+    asset_dirs: ["src/com/android/bluetooth/btservice/storage/schemas"],
+
+    // Include all test java files.
+    srcs: ["src/**/*.java"],
+
+    platform_apis: true,
+
+    test_suites: ["device-tests"],
+
+    instrumentation_for: "Bluetooth",
+
+}
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
deleted file mode 100644
index 9bfd4ab..0000000
--- a/tests/unit/Android.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-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 :=  \
-    androidx.test.rules \
-    mockito-target \
-    androidx.test.espresso.intents \
-    gson-prebuilt-jar \
-    bt-androidx-room-migration-nodeps \
-    bt-androidx-room-runtime-nodeps \
-    bt-androidx-room-testing-nodeps
-
-LOCAL_ASSET_DIR += \
-    $(LOCAL_PATH)/src/com/android/bluetooth/btservice/storage/schemas
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := BluetoothInstrumentationTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_INSTRUMENTATION_FOR := Bluetooth
-
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-
-ROOM_LIBS_PATH := ../../lib/room
-
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
-    bt-androidx-room-migration-nodeps:$(ROOM_LIBS_PATH)/room-migration-2.0.0-beta01.jar \
-    bt-androidx-room-runtime-nodeps:$(ROOM_LIBS_PATH)/room-runtime-2.0.0-alpha1.aar \
-    bt-androidx-room-testing-nodeps:$(ROOM_LIBS_PATH)/room-testing-2.0.0-alpha1.aar
-
-include $(BUILD_MULTI_PREBUILT)
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index 10c5d6d..eaf7fdc 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -29,6 +29,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH_STACK" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.MANAGE_USERS"/>
+    <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"/>
     <uses-permission android:name="com.google.android.gallery3d.permission.GALLERY_PROVIDER"/>
     <uses-permission android:name="com.android.gallery3d.permission.GALLERY_PROVIDER"/>
 
diff --git a/tests/unit/src/com/android/bluetooth/StateMachineTest.java b/tests/unit/src/com/android/bluetooth/StateMachineTest.java
new file mode 100644
index 0000000..5cf2255
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/StateMachineTest.java
@@ -0,0 +1,1985 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import android.os.Debug;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
+import com.android.bluetooth.statemachine.StateMachine.LogRec;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for StateMachine.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class StateMachineTest {
+    private static final String ENTER = "enter";
+    private static final String EXIT = "exit";
+    private static final String ON_QUITTING = "ON_QUITTING";
+
+    private static final int TEST_CMD_1 = 1;
+    private static final int TEST_CMD_2 = 2;
+    private static final int TEST_CMD_3 = 3;
+    private static final int TEST_CMD_4 = 4;
+    private static final int TEST_CMD_5 = 5;
+    private static final int TEST_CMD_6 = 6;
+
+    private static final boolean DBG = true;
+    private static final boolean WAIT_FOR_DEBUGGER = false;
+    private static final String TAG = "StateMachineTest";
+
+    private void sleep(int millis) {
+        try {
+            Thread.sleep(millis);
+        } catch(InterruptedException e) {
+        }
+    }
+
+    private void dumpLogRecs(StateMachine sm) {
+        int size = sm.getLogRecSize();
+        tlog("size=" + size + " count=" + sm.getLogRecCount());
+        for (int i = 0; i < size; i++) {
+            LogRec lr = sm.getLogRec(i);
+            tlog(lr.toString());
+        }
+    }
+
+    private void dumpLogRecs(Collection<LogRec> clr) {
+        int size = clr.size();
+        tlog("size=" + size);
+        for (LogRec lr : clr) {
+            tlog(lr.toString());
+        }
+    }
+
+    /**
+     * Tests {@link StateMachine#toString()}.
+     */
+    class StateMachineToStringTest extends StateMachine {
+        StateMachineToStringTest(String name) {
+            super(name);
+        }
+    }
+
+    class ExampleState extends State {
+        String mName;
+
+        ExampleState(String name) {
+            mName = name;
+        }
+
+        @Override
+        public String getName() {
+            return mName;
+        }
+    }
+
+    @Test
+    public void testToStringSucceedsEvenIfMachineHasNoStates() throws Exception {
+        StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+        Assert.assertTrue(stateMachine.toString().contains("TestStateMachine"));
+    }
+
+    @Test
+    public void testToStringSucceedsEvenIfStateHasNoName() throws Exception {
+        StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+        State exampleState = new ExampleState(null);
+        stateMachine.addState(exampleState);
+        stateMachine.setInitialState(exampleState);
+        stateMachine.start();
+        Assert.assertTrue(stateMachine.toString().contains("TestStateMachine"));
+        Assert.assertTrue(stateMachine.toString().contains("(null)"));
+    }
+
+    @Test
+    public void testToStringIncludesMachineAndStateNames() throws Exception {
+        StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+        State exampleState = new ExampleState("exampleState");
+        stateMachine.addState(exampleState);
+        stateMachine.setInitialState(exampleState);
+        stateMachine.start();
+        Assert.assertTrue(stateMachine.toString().contains("TestStateMachine"));
+        Assert.assertTrue(stateMachine.toString().contains("exampleState"));
+    }
+
+    @Test
+    public void testToStringDoesNotContainMultipleLines() throws Exception {
+        StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+        State exampleState = new ExampleState("exampleState");
+        stateMachine.addState(exampleState);
+        stateMachine.setInitialState(exampleState);
+        stateMachine.start();
+        Assert.assertFalse(stateMachine.toString().contains("\n"));
+    }
+
+    /**
+     * Tests {@link StateMachine#quit()}.
+     */
+    class StateMachineQuitTest extends StateMachine {
+        Collection<LogRec> mLogRecs;
+
+        StateMachineQuitTest(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+
+            // Set the initial state
+            setInitialState(mS1);
+        }
+
+        @Override
+        public void onQuitting() {
+            tlog("onQuitting");
+            addLogRec(ON_QUITTING);
+            mLogRecs = mThisSm.copyLogRecs();
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        class S1 extends State {
+            @Override
+            public void exit() {
+                tlog("S1.exit");
+                addLogRec(EXIT);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    // Sleep and assume the other messages will be queued up.
+                    case TEST_CMD_1: {
+                        tlog("TEST_CMD_1");
+                        sleep(500);
+                        quit();
+                        break;
+                    }
+                    default: {
+                        tlog("default what=" + message.what);
+                        break;
+                    }
+                }
+                return HANDLED;
+            }
+        }
+
+        private StateMachineQuitTest mThisSm;
+        private S1 mS1 = new S1();
+    }
+
+    @Test
+    public void testStateMachineQuit() throws Exception {
+        if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+        StateMachineQuitTest smQuitTest = new StateMachineQuitTest("smQuitTest");
+        smQuitTest.start();
+        if (smQuitTest.isDbg()) tlog("testStateMachineQuit E");
+
+        synchronized (smQuitTest) {
+
+            // Send 6 message we'll quit on the first but all 6 should be processed before quitting.
+            for (int i = 1; i <= 6; i++) {
+                smQuitTest.sendMessage(smQuitTest.obtainMessage(i));
+            }
+
+            try {
+                // wait for the messages to be handled
+                smQuitTest.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachineQuit: exception while waiting " + e.getMessage());
+            }
+        }
+
+        dumpLogRecs(smQuitTest.mLogRecs);
+        Assert.assertEquals(8, smQuitTest.mLogRecs.size());
+
+        LogRec lr;
+        Iterator<LogRec> itr = smQuitTest.mLogRecs.iterator();
+        for (int i = 1; i <= 6; i++) {
+            lr = itr.next();
+            Assert.assertEquals(i, lr.getWhat());
+            Assert.assertEquals(smQuitTest.mS1, lr.getState());
+            Assert.assertEquals(smQuitTest.mS1, lr.getOriginalState());
+        }
+        lr = itr.next();
+        Assert.assertEquals(EXIT, lr.getInfo());
+        Assert.assertEquals(smQuitTest.mS1, lr.getState());
+
+        lr = itr.next();
+        Assert.assertEquals(ON_QUITTING, lr.getInfo());
+
+        if (smQuitTest.isDbg()) tlog("testStateMachineQuit X");
+    }
+
+    /**
+     * Tests {@link StateMachine#quitNow()}
+     */
+    class StateMachineQuitNowTest extends StateMachine {
+        public Collection<LogRec> mLogRecs = null;
+
+        StateMachineQuitNowTest(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+
+            // Set the initial state
+            setInitialState(mS1);
+        }
+
+        @Override
+        public void onQuitting() {
+            tlog("onQuitting");
+            addLogRec(ON_QUITTING);
+            // Get a copy of the log records since we're quitting and they will disappear
+            mLogRecs = mThisSm.copyLogRecs();
+
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        class S1 extends State {
+            @Override
+            public void exit() {
+                tlog("S1.exit");
+                addLogRec(EXIT);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    // Sleep and assume the other messages will be queued up.
+                    case TEST_CMD_1: {
+                        tlog("TEST_CMD_1");
+                        sleep(500);
+                        quitNow();
+                        break;
+                    }
+                    default: {
+                        tlog("default what=" + message.what);
+                        break;
+                    }
+                }
+                return HANDLED;
+            }
+        }
+
+        private StateMachineQuitNowTest mThisSm;
+        private S1 mS1 = new S1();
+    }
+
+    @Test
+    public void testStateMachineQuitNow() throws Exception {
+        if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+        StateMachineQuitNowTest smQuitNowTest = new StateMachineQuitNowTest("smQuitNowTest");
+        smQuitNowTest.start();
+        if (smQuitNowTest.isDbg()) tlog("testStateMachineQuitNow E");
+
+        synchronized (smQuitNowTest) {
+
+            // Send 6 message we'll QuitNow on the first even though
+            // we send 6 only one will be processed.
+            for (int i = 1; i <= 6; i++) {
+                smQuitNowTest.sendMessage(smQuitNowTest.obtainMessage(i));
+            }
+
+            try {
+                // wait for the messages to be handled
+                smQuitNowTest.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachineQuitNow: exception while waiting " + e.getMessage());
+            }
+        }
+
+        tlog("testStateMachineQuiteNow: logRecs=" + smQuitNowTest.mLogRecs);
+        Assert.assertEquals(3, smQuitNowTest.mLogRecs.size());
+
+        Iterator<LogRec> itr = smQuitNowTest.mLogRecs.iterator();
+        LogRec lr = itr.next();
+        Assert.assertEquals(1, lr.getWhat());
+        Assert.assertEquals(smQuitNowTest.mS1, lr.getState());
+        Assert.assertEquals(smQuitNowTest.mS1, lr.getOriginalState());
+
+        lr = itr.next();
+        Assert.assertEquals(EXIT, lr.getInfo());
+        Assert.assertEquals(smQuitNowTest.mS1, lr.getState());
+
+        lr = itr.next();
+        Assert.assertEquals(ON_QUITTING, lr.getInfo());
+
+        if (smQuitNowTest.isDbg()) tlog("testStateMachineQuitNow X");
+    }
+
+    /**
+     * Tests {@link StateMachine#quitNow()} immediately after {@link StateMachine#start()}.
+     */
+    class StateMachineQuitNowAfterStartTest extends StateMachine {
+        Collection<LogRec> mLogRecs;
+
+        StateMachineQuitNowAfterStartTest(String name, Looper looper) {
+            super(name, looper);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+
+            // Set the initial state
+            setInitialState(mS1);
+        }
+
+        @Override
+        public void onQuitting() {
+            tlog("onQuitting");
+            addLogRec(ON_QUITTING);
+            mLogRecs = mThisSm.copyLogRecs();
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        class S1 extends State {
+            @Override
+            public void enter() {
+                tlog("S1.enter");
+                addLogRec(ENTER);
+            }
+            @Override
+            public void exit() {
+                tlog("S1.exit");
+                addLogRec(EXIT);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    // Sleep and assume the other messages will be queued up.
+                    case TEST_CMD_1: {
+                        tlog("TEST_CMD_1");
+                        sleep(500);
+                        break;
+                    }
+                    default: {
+                        tlog("default what=" + message.what);
+                        break;
+                    }
+                }
+                return HANDLED;
+            }
+        }
+
+        private StateMachineQuitNowAfterStartTest mThisSm;
+        private S1 mS1 = new S1();
+    }
+
+    /**
+     * Test enter/exit can use transitionTo
+     */
+    class StateMachineEnterExitTransitionToTest extends StateMachine {
+
+        StateMachineEnterExitTransitionToTest(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+            addState(mS2);
+            addState(mS3);
+            addState(mS4);
+
+            // Set the initial state
+            setInitialState(mS1);
+        }
+
+        class S1 extends State {
+            @Override
+            public void enter() {
+                // Test transitions in enter on the initial state work
+                addLogRec(ENTER);
+                transitionTo(mS2);
+                tlog("S1.enter");
+            }
+            @Override
+            public void exit() {
+                addLogRec(EXIT);
+                tlog("S1.exit");
+            }
+        }
+
+        class S2 extends State {
+            @Override
+            public void enter() {
+                addLogRec(ENTER);
+                tlog("S2.enter");
+            }
+            @Override
+            public void exit() {
+                // Test transition in exit work
+                transitionTo(mS4);
+
+                Assert.assertEquals(TEST_CMD_1, getCurrentMessage().what);
+                addLogRec(EXIT);
+
+                tlog("S2.exit");
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                // Start a transition to S3 but it will be
+                // changed to a transition to S4 in exit
+                transitionTo(mS3);
+                tlog("S2.processMessage");
+                return HANDLED;
+            }
+        }
+
+        class S3 extends State {
+            @Override
+            public void enter() {
+                addLogRec(ENTER);
+                tlog("S3.enter");
+            }
+            @Override
+            public void exit() {
+                addLogRec(EXIT);
+                tlog("S3.exit");
+            }
+        }
+
+        class S4 extends State {
+            @Override
+            public void enter() {
+                addLogRec(ENTER);
+                // Test that we can do halting in an enter/exit
+                transitionToHaltingState();
+                tlog("S4.enter");
+            }
+            @Override
+            public void exit() {
+                addLogRec(EXIT);
+                tlog("S4.exit");
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachineEnterExitTransitionToTest mThisSm;
+        private S1 mS1 = new S1();
+        private S2 mS2 = new S2();
+        private S3 mS3 = new S3();
+        private S4 mS4 = new S4();
+    }
+
+    @Test
+    public void testStateMachineEnterExitTransitionToTest() throws Exception {
+        //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+        StateMachineEnterExitTransitionToTest smEnterExitTranstionToTest =
+                new StateMachineEnterExitTransitionToTest("smEnterExitTranstionToTest");
+        smEnterExitTranstionToTest.start();
+        if (smEnterExitTranstionToTest.isDbg()) {
+            tlog("testStateMachineEnterExitTransitionToTest E");
+        }
+
+        synchronized (smEnterExitTranstionToTest) {
+            smEnterExitTranstionToTest.sendMessage(TEST_CMD_1);
+
+            try {
+                // wait for the messages to be handled
+                smEnterExitTranstionToTest.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachineEnterExitTransitionToTest: exception while waiting "
+                        + e.getMessage());
+            }
+        }
+
+        dumpLogRecs(smEnterExitTranstionToTest);
+
+        Assert.assertEquals(9, smEnterExitTranstionToTest.getLogRecCount());
+        LogRec lr;
+
+        lr = smEnterExitTranstionToTest.getLogRec(0);
+        Assert.assertEquals(ENTER, lr.getInfo());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS1, lr.getState());
+
+        lr = smEnterExitTranstionToTest.getLogRec(1);
+        Assert.assertEquals(EXIT, lr.getInfo());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS1, lr.getState());
+
+        lr = smEnterExitTranstionToTest.getLogRec(2);
+        Assert.assertEquals(ENTER, lr.getInfo());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getState());
+
+        lr = smEnterExitTranstionToTest.getLogRec(3);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getOriginalState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getDestState());
+
+        lr = smEnterExitTranstionToTest.getLogRec(4);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getOriginalState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState());
+        Assert.assertEquals(EXIT, lr.getInfo());
+
+        lr = smEnterExitTranstionToTest.getLogRec(5);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(ENTER, lr.getInfo());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getOriginalState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState());
+
+        lr = smEnterExitTranstionToTest.getLogRec(6);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(EXIT, lr.getInfo());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getOriginalState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState());
+
+        lr = smEnterExitTranstionToTest.getLogRec(7);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(ENTER, lr.getInfo());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getOriginalState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState());
+
+        lr = smEnterExitTranstionToTest.getLogRec(8);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(EXIT, lr.getInfo());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getState());
+        Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getOriginalState());
+
+        if (smEnterExitTranstionToTest.isDbg()) {
+            tlog("testStateMachineEnterExitTransitionToTest X");
+        }
+    }
+
+    /**
+     * Tests that ProcessedMessage works as a circular buffer.
+     */
+    class StateMachine0 extends StateMachine {
+        StateMachine0(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+            setLogRecSize(3);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+
+            // Set the initial state
+            setInitialState(mS1);
+        }
+
+        class S1 extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                if (message.what == TEST_CMD_6) {
+                    transitionToHaltingState();
+                }
+                return HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachine0 mThisSm;
+        private S1 mS1 = new S1();
+    }
+
+    @Test
+    public void testStateMachine0() throws Exception {
+        //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+        StateMachine0 sm0 = new StateMachine0("sm0");
+        sm0.start();
+        if (sm0.isDbg()) tlog("testStateMachine0 E");
+
+        synchronized (sm0) {
+            // Send 6 messages
+            for (int i = 1; i <= 6; i++) {
+                sm0.sendMessage(sm0.obtainMessage(i));
+            }
+
+            try {
+                // wait for the messages to be handled
+                sm0.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachine0: exception while waiting " + e.getMessage());
+            }
+        }
+
+        Assert.assertEquals(6, sm0.getLogRecCount());
+        Assert.assertEquals(3, sm0.getLogRecSize());
+
+        dumpLogRecs(sm0);
+
+        LogRec lr;
+        lr = sm0.getLogRec(0);
+        Assert.assertEquals(TEST_CMD_4, lr.getWhat());
+        Assert.assertEquals(sm0.mS1, lr.getState());
+        Assert.assertEquals(sm0.mS1, lr.getOriginalState());
+
+        lr = sm0.getLogRec(1);
+        Assert.assertEquals(TEST_CMD_5, lr.getWhat());
+        Assert.assertEquals(sm0.mS1, lr.getState());
+        Assert.assertEquals(sm0.mS1, lr.getOriginalState());
+
+        lr = sm0.getLogRec(2);
+        Assert.assertEquals(TEST_CMD_6, lr.getWhat());
+        Assert.assertEquals(sm0.mS1, lr.getState());
+        Assert.assertEquals(sm0.mS1, lr.getOriginalState());
+
+        if (sm0.isDbg()) tlog("testStateMachine0 X");
+    }
+
+    /**
+     * This tests enter/exit and transitions to the same state.
+     * The state machine has one state, it receives two messages
+     * in state mS1. With the first message it transitions to
+     * itself which causes it to be exited and reentered.
+     */
+    class StateMachine1 extends StateMachine {
+        StateMachine1(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+
+            // Set the initial state
+            setInitialState(mS1);
+            if (DBG) tlog("StateMachine1: ctor X");
+        }
+
+        class S1 extends State {
+            @Override
+            public void enter() {
+                mEnterCount++;
+            }
+            @Override
+            public void exit() {
+                mExitCount++;
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                if (message.what == TEST_CMD_1) {
+                    Assert.assertEquals(1, mEnterCount);
+                    Assert.assertEquals(0, mExitCount);
+                    transitionTo(mS1);
+                } else if (message.what == TEST_CMD_2) {
+                    Assert.assertEquals(2, mEnterCount);
+                    Assert.assertEquals(1, mExitCount);
+                    transitionToHaltingState();
+                }
+                return HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachine1 mThisSm;
+        private S1 mS1 = new S1();
+
+        private int mEnterCount;
+        private int mExitCount;
+    }
+
+    @Test @MediumTest
+    public void testStateMachine1() throws Exception {
+        StateMachine1 sm1 = new StateMachine1("sm1");
+        sm1.start();
+        if (sm1.isDbg()) tlog("testStateMachine1 E");
+
+        synchronized (sm1) {
+            // Send two messages
+            sm1.sendMessage(TEST_CMD_1);
+            sm1.sendMessage(TEST_CMD_2);
+
+            try {
+                // wait for the messages to be handled
+                sm1.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachine1: exception while waiting " + e.getMessage());
+            }
+        }
+
+        Assert.assertEquals(2, sm1.mEnterCount);
+        Assert.assertEquals(2, sm1.mExitCount);
+
+        Assert.assertEquals(2, sm1.getLogRecSize());
+
+        LogRec lr;
+        lr = sm1.getLogRec(0);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(sm1.mS1, lr.getState());
+        Assert.assertEquals(sm1.mS1, lr.getOriginalState());
+
+        lr = sm1.getLogRec(1);
+        Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+        Assert.assertEquals(sm1.mS1, lr.getState());
+        Assert.assertEquals(sm1.mS1, lr.getOriginalState());
+
+        Assert.assertEquals(2, sm1.mEnterCount);
+        Assert.assertEquals(2, sm1.mExitCount);
+
+        if (sm1.isDbg()) tlog("testStateMachine1 X");
+    }
+
+    /**
+     * Test deferring messages and states with no parents. The state machine
+     * has two states, it receives two messages in state mS1 deferring them
+     * until what == TEST_CMD_2 and then transitions to state mS2. State
+     * mS2 then receives both of the deferred messages first TEST_CMD_1 and
+     * then TEST_CMD_2.
+     */
+    class StateMachine2 extends StateMachine {
+        StateMachine2(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup the hierarchy
+            addState(mS1);
+            addState(mS2);
+
+            // Set the initial state
+            setInitialState(mS1);
+            if (DBG) tlog("StateMachine2: ctor X");
+        }
+
+        class S1 extends State {
+            @Override
+            public void enter() {
+                mDidEnter = true;
+            }
+            @Override
+            public void exit() {
+                mDidExit = true;
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                deferMessage(message);
+                if (message.what == TEST_CMD_2) {
+                    transitionTo(mS2);
+                }
+                return HANDLED;
+            }
+        }
+
+        class S2 extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                if (message.what == TEST_CMD_2) {
+                    transitionToHaltingState();
+                }
+                return HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachine2 mThisSm;
+        private S1 mS1 = new S1();
+        private S2 mS2 = new S2();
+
+        private boolean mDidEnter = false;
+        private boolean mDidExit = false;
+    }
+
+    @Test @MediumTest
+    public void testStateMachine2() throws Exception {
+        StateMachine2 sm2 = new StateMachine2("sm2");
+        sm2.start();
+        if (sm2.isDbg()) tlog("testStateMachine2 E");
+
+        synchronized (sm2) {
+            // Send two messages
+            sm2.sendMessage(TEST_CMD_1);
+            sm2.sendMessage(TEST_CMD_2);
+
+            try {
+                // wait for the messages to be handled
+                sm2.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachine2: exception while waiting " + e.getMessage());
+            }
+        }
+
+        Assert.assertEquals(4, sm2.getLogRecSize());
+
+        LogRec lr;
+        lr = sm2.getLogRec(0);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(sm2.mS1, lr.getState());
+
+        lr = sm2.getLogRec(1);
+        Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+        Assert.assertEquals(sm2.mS1, lr.getState());
+
+        lr = sm2.getLogRec(2);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(sm2.mS2, lr.getState());
+
+        lr = sm2.getLogRec(3);
+        Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+        Assert.assertEquals(sm2.mS2, lr.getState());
+
+        Assert.assertTrue(sm2.mDidEnter);
+        Assert.assertTrue(sm2.mDidExit);
+
+        if (sm2.isDbg()) tlog("testStateMachine2 X");
+    }
+
+    /**
+     * Test that unhandled messages in a child are handled by the parent.
+     * When TEST_CMD_2 is received.
+     */
+    class StateMachine3 extends StateMachine {
+        StateMachine3(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup the simplest hierarchy of two states
+            // mParentState and mChildState.
+            // (Use indentation to help visualize hierarchy)
+            addState(mParentState);
+            addState(mChildState, mParentState);
+
+            // Set the initial state will be the child
+            setInitialState(mChildState);
+            if (DBG) tlog("StateMachine3: ctor X");
+        }
+
+        class ParentState extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                if (message.what == TEST_CMD_2) {
+                    transitionToHaltingState();
+                }
+                return HANDLED;
+            }
+        }
+
+        class ChildState extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                return NOT_HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachine3 mThisSm;
+        private ParentState mParentState = new ParentState();
+        private ChildState mChildState = new ChildState();
+    }
+
+    @Test @MediumTest
+    public void testStateMachine3() throws Exception {
+        StateMachine3 sm3 = new StateMachine3("sm3");
+        sm3.start();
+        if (sm3.isDbg()) tlog("testStateMachine3 E");
+
+        synchronized (sm3) {
+            // Send two messages
+            sm3.sendMessage(TEST_CMD_1);
+            sm3.sendMessage(TEST_CMD_2);
+
+            try {
+                // wait for the messages to be handled
+                sm3.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachine3: exception while waiting " + e.getMessage());
+            }
+        }
+
+        Assert.assertEquals(2, sm3.getLogRecSize());
+
+        LogRec lr;
+        lr = sm3.getLogRec(0);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(sm3.mParentState, lr.getState());
+        Assert.assertEquals(sm3.mChildState, lr.getOriginalState());
+
+        lr = sm3.getLogRec(1);
+        Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+        Assert.assertEquals(sm3.mParentState, lr.getState());
+        Assert.assertEquals(sm3.mChildState, lr.getOriginalState());
+
+        if (sm3.isDbg()) tlog("testStateMachine3 X");
+    }
+
+    /**
+     * Test a hierarchy of 3 states a parent and two children
+     * with transition from child 1 to child 2 and child 2
+     * lets the parent handle the messages.
+     */
+    class StateMachine4 extends StateMachine {
+        StateMachine4(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup a hierarchy of three states
+            // mParentState, mChildState1 & mChildState2
+            // (Use indentation to help visualize hierarchy)
+            addState(mParentState);
+            addState(mChildState1, mParentState);
+            addState(mChildState2, mParentState);
+
+            // Set the initial state will be child 1
+            setInitialState(mChildState1);
+            if (DBG) tlog("StateMachine4: ctor X");
+        }
+
+        class ParentState extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                if (message.what == TEST_CMD_2) {
+                    transitionToHaltingState();
+                }
+                return HANDLED;
+            }
+        }
+
+        class ChildState1 extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                transitionTo(mChildState2);
+                return HANDLED;
+            }
+        }
+
+        class ChildState2 extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                return NOT_HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachine4 mThisSm;
+        private ParentState mParentState = new ParentState();
+        private ChildState1 mChildState1 = new ChildState1();
+        private ChildState2 mChildState2 = new ChildState2();
+    }
+
+    @Test @MediumTest
+    public void testStateMachine4() throws Exception {
+        StateMachine4 sm4 = new StateMachine4("sm4");
+        sm4.start();
+        if (sm4.isDbg()) tlog("testStateMachine4 E");
+
+        synchronized (sm4) {
+            // Send two messages
+            sm4.sendMessage(TEST_CMD_1);
+            sm4.sendMessage(TEST_CMD_2);
+
+            try {
+                // wait for the messages to be handled
+                sm4.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachine4: exception while waiting " + e.getMessage());
+            }
+        }
+
+
+        Assert.assertEquals(2, sm4.getLogRecSize());
+
+        LogRec lr;
+        lr = sm4.getLogRec(0);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(sm4.mChildState1, lr.getState());
+        Assert.assertEquals(sm4.mChildState1, lr.getOriginalState());
+
+        lr = sm4.getLogRec(1);
+        Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+        Assert.assertEquals(sm4.mParentState, lr.getState());
+        Assert.assertEquals(sm4.mChildState2, lr.getOriginalState());
+
+        if (sm4.isDbg()) tlog("testStateMachine4 X");
+    }
+
+    /**
+     * Test transition from one child to another of a "complex"
+     * hierarchy with two parents and multiple children.
+     */
+    class StateMachine5 extends StateMachine {
+        StateMachine5(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup a hierarchy with two parents and some children.
+            // (Use indentation to help visualize hierarchy)
+            addState(mParentState1);
+            addState(mChildState1, mParentState1);
+            addState(mChildState2, mParentState1);
+
+            addState(mParentState2);
+            addState(mChildState3, mParentState2);
+            addState(mChildState4, mParentState2);
+            addState(mChildState5, mChildState4);
+
+            // Set the initial state will be the child
+            setInitialState(mChildState1);
+            if (DBG) tlog("StateMachine5: ctor X");
+        }
+
+        class ParentState1 extends State {
+            @Override
+            public void enter() {
+                mParentState1EnterCount += 1;
+            }
+            @Override
+            public void exit() {
+                mParentState1ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                return HANDLED;
+            }
+        }
+
+        class ChildState1 extends State {
+            @Override
+            public void enter() {
+                mChildState1EnterCount += 1;
+            }
+            @Override
+            public void exit() {
+                mChildState1ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Assert.assertEquals(1, mParentState1EnterCount);
+                Assert.assertEquals(0, mParentState1ExitCount);
+                Assert.assertEquals(1, mChildState1EnterCount);
+                Assert.assertEquals(0, mChildState1ExitCount);
+                Assert.assertEquals(0, mChildState2EnterCount);
+                Assert.assertEquals(0, mChildState2ExitCount);
+                Assert.assertEquals(0, mParentState2EnterCount);
+                Assert.assertEquals(0, mParentState2ExitCount);
+                Assert.assertEquals(0, mChildState3EnterCount);
+                Assert.assertEquals(0, mChildState3ExitCount);
+                Assert.assertEquals(0, mChildState4EnterCount);
+                Assert.assertEquals(0, mChildState4ExitCount);
+                Assert.assertEquals(0, mChildState5EnterCount);
+                Assert.assertEquals(0, mChildState5ExitCount);
+
+                transitionTo(mChildState2);
+                return HANDLED;
+            }
+        }
+
+        class ChildState2 extends State {
+            @Override
+            public void enter() {
+                mChildState2EnterCount += 1;
+            }
+            @Override
+            public void exit() {
+                mChildState2ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Assert.assertEquals(1, mParentState1EnterCount);
+                Assert.assertEquals(0, mParentState1ExitCount);
+                Assert.assertEquals(1, mChildState1EnterCount);
+                Assert.assertEquals(1, mChildState1ExitCount);
+                Assert.assertEquals(1, mChildState2EnterCount);
+                Assert.assertEquals(0, mChildState2ExitCount);
+                Assert.assertEquals(0, mParentState2EnterCount);
+                Assert.assertEquals(0, mParentState2ExitCount);
+                Assert.assertEquals(0, mChildState3EnterCount);
+                Assert.assertEquals(0, mChildState3ExitCount);
+                Assert.assertEquals(0, mChildState4EnterCount);
+                Assert.assertEquals(0, mChildState4ExitCount);
+                Assert.assertEquals(0, mChildState5EnterCount);
+                Assert.assertEquals(0, mChildState5ExitCount);
+
+                transitionTo(mChildState5);
+                return HANDLED;
+            }
+        }
+
+        class ParentState2 extends State {
+            @Override
+            public void enter() {
+                mParentState2EnterCount += 1;
+            }
+            @Override
+            public void exit() {
+                mParentState2ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Assert.assertEquals(1, mParentState1EnterCount);
+                Assert.assertEquals(1, mParentState1ExitCount);
+                Assert.assertEquals(1, mChildState1EnterCount);
+                Assert.assertEquals(1, mChildState1ExitCount);
+                Assert.assertEquals(1, mChildState2EnterCount);
+                Assert.assertEquals(1, mChildState2ExitCount);
+                Assert.assertEquals(2, mParentState2EnterCount);
+                Assert.assertEquals(1, mParentState2ExitCount);
+                Assert.assertEquals(1, mChildState3EnterCount);
+                Assert.assertEquals(1, mChildState3ExitCount);
+                Assert.assertEquals(2, mChildState4EnterCount);
+                Assert.assertEquals(2, mChildState4ExitCount);
+                Assert.assertEquals(1, mChildState5EnterCount);
+                Assert.assertEquals(1, mChildState5ExitCount);
+
+                transitionToHaltingState();
+                return HANDLED;
+            }
+        }
+
+        class ChildState3 extends State {
+            @Override
+            public void enter() {
+                mChildState3EnterCount += 1;
+            }
+            @Override
+            public void exit() {
+                mChildState3ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Assert.assertEquals(1, mParentState1EnterCount);
+                Assert.assertEquals(1, mParentState1ExitCount);
+                Assert.assertEquals(1, mChildState1EnterCount);
+                Assert.assertEquals(1, mChildState1ExitCount);
+                Assert.assertEquals(1, mChildState2EnterCount);
+                Assert.assertEquals(1, mChildState2ExitCount);
+                Assert.assertEquals(1, mParentState2EnterCount);
+                Assert.assertEquals(0, mParentState2ExitCount);
+                Assert.assertEquals(1, mChildState3EnterCount);
+                Assert.assertEquals(0, mChildState3ExitCount);
+                Assert.assertEquals(1, mChildState4EnterCount);
+                Assert.assertEquals(1, mChildState4ExitCount);
+                Assert.assertEquals(1, mChildState5EnterCount);
+                Assert.assertEquals(1, mChildState5ExitCount);
+
+                transitionTo(mChildState4);
+                return HANDLED;
+            }
+        }
+
+        class ChildState4 extends State {
+            @Override
+            public void enter() {
+                mChildState4EnterCount += 1;
+            }
+            @Override
+            public void exit() {
+                mChildState4ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Assert.assertEquals(1, mParentState1EnterCount);
+                Assert.assertEquals(1, mParentState1ExitCount);
+                Assert.assertEquals(1, mChildState1EnterCount);
+                Assert.assertEquals(1, mChildState1ExitCount);
+                Assert.assertEquals(1, mChildState2EnterCount);
+                Assert.assertEquals(1, mChildState2ExitCount);
+                Assert.assertEquals(1, mParentState2EnterCount);
+                Assert.assertEquals(0, mParentState2ExitCount);
+                Assert.assertEquals(1, mChildState3EnterCount);
+                Assert.assertEquals(1, mChildState3ExitCount);
+                Assert.assertEquals(2, mChildState4EnterCount);
+                Assert.assertEquals(1, mChildState4ExitCount);
+                Assert.assertEquals(1, mChildState5EnterCount);
+                Assert.assertEquals(1, mChildState5ExitCount);
+
+                transitionTo(mParentState2);
+                return HANDLED;
+            }
+        }
+
+        class ChildState5 extends State {
+            @Override
+            public void enter() {
+                mChildState5EnterCount += 1;
+            }
+            @Override
+            public void exit() {
+                mChildState5ExitCount += 1;
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                Assert.assertEquals(1, mParentState1EnterCount);
+                Assert.assertEquals(1, mParentState1ExitCount);
+                Assert.assertEquals(1, mChildState1EnterCount);
+                Assert.assertEquals(1, mChildState1ExitCount);
+                Assert.assertEquals(1, mChildState2EnterCount);
+                Assert.assertEquals(1, mChildState2ExitCount);
+                Assert.assertEquals(1, mParentState2EnterCount);
+                Assert.assertEquals(0, mParentState2ExitCount);
+                Assert.assertEquals(0, mChildState3EnterCount);
+                Assert.assertEquals(0, mChildState3ExitCount);
+                Assert.assertEquals(1, mChildState4EnterCount);
+                Assert.assertEquals(0, mChildState4ExitCount);
+                Assert.assertEquals(1, mChildState5EnterCount);
+                Assert.assertEquals(0, mChildState5ExitCount);
+
+                transitionTo(mChildState3);
+                return HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachine5 mThisSm;
+        private ParentState1 mParentState1 = new ParentState1();
+        private ChildState1 mChildState1 = new ChildState1();
+        private ChildState2 mChildState2 = new ChildState2();
+        private ParentState2 mParentState2 = new ParentState2();
+        private ChildState3 mChildState3 = new ChildState3();
+        private ChildState4 mChildState4 = new ChildState4();
+        private ChildState5 mChildState5 = new ChildState5();
+
+        private int mParentState1EnterCount = 0;
+        private int mParentState1ExitCount = 0;
+        private int mChildState1EnterCount = 0;
+        private int mChildState1ExitCount = 0;
+        private int mChildState2EnterCount = 0;
+        private int mChildState2ExitCount = 0;
+        private int mParentState2EnterCount = 0;
+        private int mParentState2ExitCount = 0;
+        private int mChildState3EnterCount = 0;
+        private int mChildState3ExitCount = 0;
+        private int mChildState4EnterCount = 0;
+        private int mChildState4ExitCount = 0;
+        private int mChildState5EnterCount = 0;
+        private int mChildState5ExitCount = 0;
+    }
+
+    @Test @MediumTest
+    public void testStateMachine5() throws Exception {
+        StateMachine5 sm5 = new StateMachine5("sm5");
+        sm5.start();
+        if (sm5.isDbg()) tlog("testStateMachine5 E");
+
+        synchronized (sm5) {
+            // Send 6 messages
+            sm5.sendMessage(TEST_CMD_1);
+            sm5.sendMessage(TEST_CMD_2);
+            sm5.sendMessage(TEST_CMD_3);
+            sm5.sendMessage(TEST_CMD_4);
+            sm5.sendMessage(TEST_CMD_5);
+            sm5.sendMessage(TEST_CMD_6);
+
+            try {
+                // wait for the messages to be handled
+                sm5.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachine5: exception while waiting " + e.getMessage());
+            }
+        }
+
+
+        Assert.assertEquals(6, sm5.getLogRecSize());
+
+        Assert.assertEquals(1, sm5.mParentState1EnterCount);
+        Assert.assertEquals(1, sm5.mParentState1ExitCount);
+        Assert.assertEquals(1, sm5.mChildState1EnterCount);
+        Assert.assertEquals(1, sm5.mChildState1ExitCount);
+        Assert.assertEquals(1, sm5.mChildState2EnterCount);
+        Assert.assertEquals(1, sm5.mChildState2ExitCount);
+        Assert.assertEquals(2, sm5.mParentState2EnterCount);
+        Assert.assertEquals(2, sm5.mParentState2ExitCount);
+        Assert.assertEquals(1, sm5.mChildState3EnterCount);
+        Assert.assertEquals(1, sm5.mChildState3ExitCount);
+        Assert.assertEquals(2, sm5.mChildState4EnterCount);
+        Assert.assertEquals(2, sm5.mChildState4ExitCount);
+        Assert.assertEquals(1, sm5.mChildState5EnterCount);
+        Assert.assertEquals(1, sm5.mChildState5ExitCount);
+
+        LogRec lr;
+        lr = sm5.getLogRec(0);
+        Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+        Assert.assertEquals(sm5.mChildState1, lr.getState());
+        Assert.assertEquals(sm5.mChildState1, lr.getOriginalState());
+
+        lr = sm5.getLogRec(1);
+        Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+        Assert.assertEquals(sm5.mChildState2, lr.getState());
+        Assert.assertEquals(sm5.mChildState2, lr.getOriginalState());
+
+        lr = sm5.getLogRec(2);
+        Assert.assertEquals(TEST_CMD_3, lr.getWhat());
+        Assert.assertEquals(sm5.mChildState5, lr.getState());
+        Assert.assertEquals(sm5.mChildState5, lr.getOriginalState());
+
+        lr = sm5.getLogRec(3);
+        Assert.assertEquals(TEST_CMD_4, lr.getWhat());
+        Assert.assertEquals(sm5.mChildState3, lr.getState());
+        Assert.assertEquals(sm5.mChildState3, lr.getOriginalState());
+
+        lr = sm5.getLogRec(4);
+        Assert.assertEquals(TEST_CMD_5, lr.getWhat());
+        Assert.assertEquals(sm5.mChildState4, lr.getState());
+        Assert.assertEquals(sm5.mChildState4, lr.getOriginalState());
+
+        lr = sm5.getLogRec(5);
+        Assert.assertEquals(TEST_CMD_6, lr.getWhat());
+        Assert.assertEquals(sm5.mParentState2, lr.getState());
+        Assert.assertEquals(sm5.mParentState2, lr.getOriginalState());
+
+        if (sm5.isDbg()) tlog("testStateMachine5 X");
+    }
+
+    /**
+     * Test that the initial state enter is invoked immediately
+     * after construction and before any other messages arrive and that
+     * sendMessageDelayed works.
+     */
+    class StateMachine6 extends StateMachine {
+        StateMachine6(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+
+            // Set the initial state
+            setInitialState(mS1);
+            if (DBG) tlog("StateMachine6: ctor X");
+        }
+
+        class S1 extends State {
+            @Override
+            public void enter() {
+                sendMessage(TEST_CMD_1);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                if (message.what == TEST_CMD_1) {
+                    mArrivalTimeMsg1 = SystemClock.elapsedRealtime();
+                } else if (message.what == TEST_CMD_2) {
+                    mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
+                    transitionToHaltingState();
+                }
+                return HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachine6 mThisSm;
+        private S1 mS1 = new S1();
+
+        private long mArrivalTimeMsg1;
+        private long mArrivalTimeMsg2;
+    }
+
+    @Test @MediumTest
+    public void testStateMachine6() throws Exception {
+        final int DELAY_TIME = 250;
+        final int DELAY_FUDGE = 20;
+
+        StateMachine6 sm6 = new StateMachine6("sm6");
+        sm6.start();
+        if (sm6.isDbg()) tlog("testStateMachine6 E");
+
+        synchronized (sm6) {
+            // Send a message
+            sm6.sendMessageDelayed(TEST_CMD_2, DELAY_TIME);
+
+            try {
+                // wait for the messages to be handled
+                sm6.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachine6: exception while waiting " + e.getMessage());
+            }
+        }
+
+        /**
+         * TEST_CMD_1 was sent in enter and must always have been processed
+         * immediately after construction and hence the arrival time difference
+         * should always >= to the DELAY_TIME
+         */
+        long arrivalTimeDiff = sm6.mArrivalTimeMsg2 - sm6.mArrivalTimeMsg1;
+        long expectedDelay = DELAY_TIME - DELAY_FUDGE;
+        if (sm6.isDbg()) tlog("testStateMachine6: expect " + arrivalTimeDiff
+                + " >= " + expectedDelay);
+        Assert.assertTrue(arrivalTimeDiff >= expectedDelay);
+
+        if (sm6.isDbg()) tlog("testStateMachine6 X");
+    }
+
+    /**
+     * Test that enter is invoked immediately after exit. This validates
+     * that enter can be used to send a watch dog message for its state.
+     */
+    class StateMachine7 extends StateMachine {
+        private final int SM7_DELAY_TIME = 250;
+
+        StateMachine7(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+            addState(mS2);
+
+            // Set the initial state
+            setInitialState(mS1);
+            if (DBG) tlog("StateMachine7: ctor X");
+        }
+
+        class S1 extends State {
+            @Override
+            public void exit() {
+                sendMessage(TEST_CMD_2);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                transitionTo(mS2);
+                return HANDLED;
+            }
+        }
+
+        class S2 extends State {
+            @Override
+            public void enter() {
+                // Send a delayed message as a watch dog
+                sendMessageDelayed(TEST_CMD_3, SM7_DELAY_TIME);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                if (message.what == TEST_CMD_2) {
+                    mMsgCount += 1;
+                    mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
+                } else if (message.what == TEST_CMD_3) {
+                    mMsgCount += 1;
+                    mArrivalTimeMsg3 = SystemClock.elapsedRealtime();
+                }
+
+                if (mMsgCount == 2) {
+                    transitionToHaltingState();
+                }
+                return HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachine7 mThisSm;
+        private S1 mS1 = new S1();
+        private S2 mS2 = new S2();
+
+        private int mMsgCount = 0;
+        private long mArrivalTimeMsg2;
+        private long mArrivalTimeMsg3;
+    }
+
+    @Test @MediumTest
+    public void testStateMachine7() throws Exception {
+        final int SM7_DELAY_FUDGE = 20;
+
+        StateMachine7 sm7 = new StateMachine7("sm7");
+        sm7.start();
+        if (sm7.isDbg()) tlog("testStateMachine7 E");
+
+        synchronized (sm7) {
+            // Send a message
+            sm7.sendMessage(TEST_CMD_1);
+
+            try {
+                // wait for the messages to be handled
+                sm7.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachine7: exception while waiting " + e.getMessage());
+            }
+        }
+
+        /**
+         * TEST_CMD_3 was sent in S2.enter with a delay and must always have been
+         * processed immediately after S1.exit. Since S1.exit sent TEST_CMD_2
+         * without a delay the arrival time difference should always >= to SM7_DELAY_TIME.
+         */
+        long arrivalTimeDiff = sm7.mArrivalTimeMsg3 - sm7.mArrivalTimeMsg2;
+        long expectedDelay = sm7.SM7_DELAY_TIME - SM7_DELAY_FUDGE;
+        if (sm7.isDbg()) tlog("testStateMachine7: expect " + arrivalTimeDiff
+                + " >= " + expectedDelay);
+        Assert.assertTrue(arrivalTimeDiff >= expectedDelay);
+
+        if (sm7.isDbg()) tlog("testStateMachine7 X");
+    }
+
+    /**
+     * Test unhandledMessage.
+     */
+    class StateMachineUnhandledMessage extends StateMachine {
+        StateMachineUnhandledMessage(String name) {
+            super(name);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+
+            // Set the initial state
+            setInitialState(mS1);
+        }
+        @Override
+        public void unhandledMessage(Message message) {
+            mUnhandledMessageCount += 1;
+        }
+
+        class S1 extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                if (message.what == TEST_CMD_2) {
+                    transitionToHaltingState();
+                }
+                return NOT_HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        private StateMachineUnhandledMessage mThisSm;
+        private int mUnhandledMessageCount;
+        private S1 mS1 = new S1();
+    }
+
+    @Test
+    public void testStateMachineUnhandledMessage() throws Exception {
+
+        StateMachineUnhandledMessage sm = new StateMachineUnhandledMessage("smUnhandledMessage");
+        sm.start();
+        if (sm.isDbg()) tlog("testStateMachineUnhandledMessage E");
+
+        synchronized (sm) {
+            // Send 2 messages
+            for (int i = 1; i <= 2; i++) {
+                sm.sendMessage(i);
+            }
+
+            try {
+                // wait for the messages to be handled
+                sm.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachineUnhandledMessage: exception while waiting "
+                        + e.getMessage());
+            }
+        }
+
+        Assert.assertEquals(2, sm.getLogRecSize());
+        Assert.assertEquals(2, sm.mUnhandledMessageCount);
+
+        if (sm.isDbg()) tlog("testStateMachineUnhandledMessage X");
+    }
+
+    /**
+     * Test state machines sharing the same thread/looper. Multiple instances
+     * of the same state machine will be created. They will all share the
+     * same thread and thus each can update <code>sharedCounter</code> which
+     * will be used to notify testStateMachineSharedThread that the test is
+     * complete.
+     */
+    class StateMachineSharedThread extends StateMachine {
+        StateMachineSharedThread(String name, Looper looper, int maxCount) {
+            super(name, looper);
+            mMaxCount = maxCount;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+
+            // Set the initial state
+            setInitialState(mS1);
+        }
+
+        class S1 extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                if (message.what == TEST_CMD_4) {
+                    transitionToHaltingState();
+                }
+                return HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            // Update the shared counter, which is OK since all state
+            // machines are using the same thread.
+            sharedCounter += 1;
+            if (sharedCounter == mMaxCount) {
+                synchronized (waitObject) {
+                    waitObject.notifyAll();
+                }
+            }
+        }
+
+        private int mMaxCount;
+        private S1 mS1 = new S1();
+    }
+    private static int sharedCounter = 0;
+    private static Object waitObject = new Object();
+
+    @Test @MediumTest
+    public void testStateMachineSharedThread() throws Exception {
+        if (DBG) tlog("testStateMachineSharedThread E");
+
+        // Create and start the handler thread
+        HandlerThread smThread = new HandlerThread("testStateMachineSharedThread");
+        smThread.start();
+
+        // Create the state machines
+        StateMachineSharedThread sms[] = new StateMachineSharedThread[10];
+        for (int i = 0; i < sms.length; i++) {
+            sms[i] = new StateMachineSharedThread("smSharedThread",
+                    smThread.getLooper(), sms.length);
+            sms[i].start();
+        }
+
+        synchronized (waitObject) {
+            // Send messages to each of the state machines
+            for (StateMachineSharedThread sm : sms) {
+                for (int i = 1; i <= 4; i++) {
+                    sm.sendMessage(i);
+                }
+            }
+
+            // Wait for the last state machine to notify its done
+            try {
+                waitObject.wait();
+            } catch (InterruptedException e) {
+                tloge("testStateMachineSharedThread: exception while waiting "
+                        + e.getMessage());
+            }
+        }
+
+        for (StateMachineSharedThread sm : sms) {
+            Assert.assertEquals(4, sm.getLogRecCount());
+            for (int i = 0; i < sm.getLogRecSize(); i++) {
+                LogRec lr = sm.getLogRec(i);
+                Assert.assertEquals(i+1, lr.getWhat());
+                Assert.assertEquals(sm.mS1, lr.getState());
+                Assert.assertEquals(sm.mS1, lr.getOriginalState());
+            }
+        }
+
+        if (DBG) tlog("testStateMachineSharedThread X");
+    }
+
+    static class Hsm1 extends StateMachine {
+        private static final String HSM1_TAG = "hsm1";
+
+        public static final int CMD_1 = 1;
+        public static final int CMD_2 = 2;
+        public static final int CMD_3 = 3;
+        public static final int CMD_4 = 4;
+        public static final int CMD_5 = 5;
+
+        public static Hsm1 makeHsm1() {
+            Log.d(HSM1_TAG, "makeHsm1 E");
+            Hsm1 sm = new Hsm1(HSM1_TAG);
+            sm.start();
+            Log.d(HSM1_TAG, "makeHsm1 X");
+            return sm;
+        }
+
+        Hsm1(String name) {
+            super(name);
+            tlog("ctor E");
+
+            // Add states, use indentation to show hierarchy
+            addState(mP1);
+            addState(mS1, mP1);
+            addState(mS2, mP1);
+            addState(mP2);
+
+            // Set the initial state
+            setInitialState(mS1);
+            tlog("ctor X");
+        }
+
+        class P1 extends State {
+            @Override
+            public void enter() {
+                tlog("P1.enter");
+            }
+            @Override
+            public void exit() {
+                tlog("P1.exit");
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                boolean retVal;
+                tlog("P1.processMessage what=" + message.what);
+                switch(message.what) {
+                    case CMD_2:
+                        // CMD_2 will arrive in mS2 before CMD_3
+                        sendMessage(CMD_3);
+                        deferMessage(message);
+                        transitionTo(mS2);
+                        retVal = true;
+                        break;
+                    default:
+                        // Any message we don't understand in this state invokes unhandledMessage
+                        retVal = false;
+                        break;
+                }
+                return retVal;
+            }
+        }
+
+        class S1 extends State {
+            @Override
+            public void enter() {
+                tlog("S1.enter");
+            }
+            @Override
+            public void exit() {
+                tlog("S1.exit");
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                tlog("S1.processMessage what=" + message.what);
+                if (message.what == CMD_1) {
+                    // Transition to ourself to show that enter/exit is called
+                    transitionTo(mS1);
+                    return HANDLED;
+                } else {
+                    // Let parent process all other messages
+                    return NOT_HANDLED;
+                }
+            }
+        }
+
+        class S2 extends State {
+            @Override
+            public void enter() {
+                tlog("S2.enter");
+            }
+            @Override
+            public void exit() {
+                tlog("S2.exit");
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                boolean retVal;
+                tlog("S2.processMessage what=" + message.what);
+                switch(message.what) {
+                    case(CMD_2):
+                        sendMessage(CMD_4);
+                        retVal = true;
+                        break;
+                    case(CMD_3):
+                        deferMessage(message);
+                        transitionTo(mP2);
+                        retVal = true;
+                        break;
+                    default:
+                        retVal = false;
+                        break;
+                }
+                return retVal;
+            }
+        }
+
+        class P2 extends State {
+            @Override
+            public void enter() {
+                tlog("P2.enter");
+                sendMessage(CMD_5);
+            }
+            @Override
+            public void exit() {
+                tlog("P2.exit");
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                tlog("P2.processMessage what=" + message.what);
+                switch(message.what) {
+                    case(CMD_3):
+                        break;
+                    case(CMD_4):
+                        break;
+                    case(CMD_5):
+                        transitionToHaltingState();
+                        break;
+                }
+                return HANDLED;
+            }
+        }
+
+        @Override
+        protected void onHalting() {
+            tlog("halting");
+            synchronized (this) {
+                this.notifyAll();
+            }
+        }
+
+        P1 mP1 = new P1();
+        S1 mS1 = new S1();
+        S2 mS2 = new S2();
+        P2 mP2 = new P2();
+    }
+
+    @Test @MediumTest
+    public void testHsm1() throws Exception {
+        if (DBG) tlog("testHsm1 E");
+
+        Hsm1 sm = Hsm1.makeHsm1();
+
+        // Send messages
+        sm.sendMessage(Hsm1.CMD_1);
+        sm.sendMessage(Hsm1.CMD_2);
+
+        synchronized (sm) {
+            // Wait for the last state machine to notify its done
+            try {
+                sm.wait();
+            } catch (InterruptedException e) {
+                tloge("testHsm1: exception while waiting " + e.getMessage());
+            }
+        }
+
+        dumpLogRecs(sm);
+
+        Assert.assertEquals(7, sm.getLogRecCount());
+
+        LogRec lr = sm.getLogRec(0);
+        Assert.assertEquals(Hsm1.CMD_1, lr.getWhat());
+        Assert.assertEquals(sm.mS1, lr.getState());
+        Assert.assertEquals(sm.mS1, lr.getOriginalState());
+
+        lr = sm.getLogRec(1);
+        Assert.assertEquals(Hsm1.CMD_2, lr.getWhat());
+        Assert.assertEquals(sm.mP1, lr.getState());
+        Assert.assertEquals(sm.mS1, lr.getOriginalState());
+
+        lr = sm.getLogRec(2);
+        Assert.assertEquals(Hsm1.CMD_2, lr.getWhat());
+        Assert.assertEquals(sm.mS2, lr.getState());
+        Assert.assertEquals(sm.mS2, lr.getOriginalState());
+
+        lr = sm.getLogRec(3);
+        Assert.assertEquals(Hsm1.CMD_3, lr.getWhat());
+        Assert.assertEquals(sm.mS2, lr.getState());
+        Assert.assertEquals(sm.mS2, lr.getOriginalState());
+
+        lr = sm.getLogRec(4);
+        Assert.assertEquals(Hsm1.CMD_3, lr.getWhat());
+        Assert.assertEquals(sm.mP2, lr.getState());
+        Assert.assertEquals(sm.mP2, lr.getOriginalState());
+
+        lr = sm.getLogRec(5);
+        Assert.assertEquals(Hsm1.CMD_4, lr.getWhat());
+        Assert.assertEquals(sm.mP2, lr.getState());
+        Assert.assertEquals(sm.mP2, lr.getOriginalState());
+
+        lr = sm.getLogRec(6);
+        Assert.assertEquals(Hsm1.CMD_5, lr.getWhat());
+        Assert.assertEquals(sm.mP2, lr.getState());
+        Assert.assertEquals(sm.mP2, lr.getOriginalState());
+
+        if (DBG) tlog("testStateMachineSharedThread X");
+    }
+
+    private static void tlog(String s) {
+        Log.d(TAG, s);
+    }
+
+    private static void tloge(String s) {
+        Log.e(TAG, s);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index 4492a45..99403eb 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -115,7 +115,7 @@
         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
         doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
                 .getBondState(any(BluetoothDevice.class));
-        doReturn(new ParcelUuid[]{BluetoothUuid.AudioSink}).when(mAdapterService)
+        doReturn(new ParcelUuid[]{BluetoothUuid.A2DP_SINK}).when(mAdapterService)
                 .getRemoteUuids(any(BluetoothDevice.class));
     }
 
@@ -260,32 +260,25 @@
     @Test
     public void testGetPriority() {
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         Assert.assertEquals("Initial device priority",
-                            BluetoothProfile.PRIORITY_UNDEFINED,
-                            mA2dpService.getPriority(mTestDevice));
+                            BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+                            mA2dpService.getConnectionPolicy(mTestDevice));
 
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         Assert.assertEquals("Setting device priority to PRIORITY_OFF",
-                            BluetoothProfile.PRIORITY_OFF,
-                            mA2dpService.getPriority(mTestDevice));
+                            BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+                            mA2dpService.getConnectionPolicy(mTestDevice));
 
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         Assert.assertEquals("Setting device priority to PRIORITY_ON",
-                            BluetoothProfile.PRIORITY_ON,
-                            mA2dpService.getPriority(mTestDevice));
-
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
-        Assert.assertEquals("Setting device priority to PRIORITY_AUTO_CONNECT",
-                            BluetoothProfile.PRIORITY_AUTO_CONNECT,
-                            mA2dpService.getPriority(mTestDevice));
+                            BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+                            mA2dpService.getConnectionPolicy(mTestDevice));
     }
 
     /**
@@ -296,43 +289,35 @@
         int badPriorityValue = 1024;
         int badBondState = 42;
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
-        testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mTestDevice,
                 BluetoothDevice.BOND_NONE, badPriorityValue, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
-        testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mTestDevice,
                 BluetoothDevice.BOND_BONDING, badPriorityValue, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
-        testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
         testOkToConnectCase(mTestDevice,
                 BluetoothDevice.BOND_BONDED, badPriorityValue, false);
         testOkToConnectCase(mTestDevice,
-                badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
+                badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToConnectCase(mTestDevice,
-                badBondState, BluetoothProfile.PRIORITY_OFF, false);
+                badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mTestDevice,
-                badBondState, BluetoothProfile.PRIORITY_ON, false);
-        testOkToConnectCase(mTestDevice,
-                badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mTestDevice,
                 badBondState, badPriorityValue, false);
     }
@@ -345,13 +330,13 @@
     public void testOutgoingConnectMissingAudioSinkUuid() {
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         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)
+        doReturn(new ParcelUuid[]{BluetoothUuid.A2DP_SOURCE}).when(mAdapterService)
                 .getRemoteUuids(any(BluetoothDevice.class));
 
         // Send a connect request
@@ -368,8 +353,8 @@
 
         // Set the device priority to PRIORITY_OFF so connect() should fail
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
 
         // Send a connect request
         Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice));
@@ -382,8 +367,8 @@
     public void testOutgoingConnectTimeout() {
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -413,8 +398,8 @@
 
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -484,8 +469,8 @@
             BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
             testDevices[i] = testDevice;
             when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-            when(mDatabaseManager.getProfilePriority(testDevice, BluetoothProfile.A2DP))
-                    .thenReturn(BluetoothProfile.PRIORITY_ON);
+            when(mDatabaseManager.getProfileConnectionPolicy(testDevice, BluetoothProfile.A2DP))
+                    .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             // Send a connect request
             Assert.assertTrue("Connect failed", mA2dpService.connect(testDevice));
             // Verify the connection state broadcast, and that we are in Connecting state
@@ -512,8 +497,8 @@
         // Prepare and connect the extra test device. The connect request should fail
         extraTestDevice = TestUtils.getTestDevice(mAdapter, MAX_CONNECTED_AUDIO_DEVICES);
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(extraTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(extraTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         // Send a connect request
         Assert.assertFalse("Connect expected to fail", mA2dpService.connect(extraTestDevice));
     }
@@ -528,8 +513,8 @@
 
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -608,8 +593,8 @@
 
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -673,8 +658,8 @@
 
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -732,8 +717,8 @@
 
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
@@ -906,8 +891,8 @@
 
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(device);
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(device);
         doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
@@ -1026,7 +1011,7 @@
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP))
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP))
                 .thenReturn(priority);
 
         // Test when the AdapterService is in non-quiet mode: the result should not depend
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
index d82d28b..c089dd3 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
@@ -92,7 +92,7 @@
      * @param priority - The priority value you want the device to have
      */
     private void mockDevicePriority(BluetoothDevice device, int priority) {
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP_SINK))
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK))
                 .thenReturn(priority);
     }
 
@@ -107,7 +107,7 @@
     @Test
     public void testConnect() {
         BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
-        mockDevicePriority(device, BluetoothProfile.PRIORITY_ON);
+        mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         Assert.assertTrue(mService.connect(device));
     }
 
@@ -117,7 +117,7 @@
     @Test
     public void testConnectPriorityOffDevice() {
         BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
-        mockDevicePriority(device, BluetoothProfile.PRIORITY_OFF);
+        mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         Assert.assertFalse(mService.connect(device));
     }
 }
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
index c759a8a..b8fbbb9 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
@@ -195,6 +195,21 @@
     }
 
     @Test
+    public void testFocusRerequest() {
+        // 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(0)).informAudioFocusStateNative(0);
+        verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0);
+        mStreamHandler.handleMessage(
+                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS, true));
+        verify(mMockAudioManager, times(2)).requestAudioFocus(any());
+    }
+
+    @Test
     public void testFocusGainTransient() {
         // Focus was lost then regained.
         testSnkPlay();
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
index 0ca9c2a..c8d421b 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
@@ -23,12 +23,14 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.media.AudioManager;
 import android.os.Looper;
 import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.rule.ServiceTestRule;
 import androidx.test.runner.AndroidJUnit4;
@@ -82,6 +84,9 @@
     @Mock
     private AvrcpControllerService mAvrcpControllerService;
 
+    @Mock
+    private Resources mMockResources;
+
     AvrcpControllerStateMachine mAvrcpStateMachine;
 
     @Before
@@ -102,7 +107,9 @@
         TestUtils.clearAdapterService(mAvrcpAdapterService);
         TestUtils.setAdapterService(mA2dpAdapterService);
         TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class);
-        doReturn(mTargetContext.getResources()).when(mAvrcpControllerService).getResources();
+        when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
+                .thenReturn(true);
+        doReturn(mMockResources).when(mAvrcpControllerService).getResources();
         doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt());
         doReturn(8).when(mAudioManager).getStreamVolume(anyInt());
         doReturn(true).when(mAudioManager).isVolumeFixed();
@@ -193,6 +200,7 @@
      * Test to confirm that a browsing only device can be established (no control)
      */
     @Test
+    @FlakyTest
     public void testBrowsingOnly() {
         Assert.assertEquals(0, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
         int numBroadcastsSent = setUpConnectedState(false, true);
@@ -343,6 +351,7 @@
      * Test media browser fast forward command
      */
     @Test
+    @FlakyTest
     public void testFastForward() throws Exception {
         setUpConnectedState(true, true);
         MediaControllerCompat.TransportControls transportControls =
@@ -451,6 +460,7 @@
      * Verify that the contents of a player are fetched upon request
      */
     @Test
+    @FlakyTest
     public void testBrowsingCommands() {
         setUpConnectedState(true, true);
         final String rootName = "__ROOT__";
@@ -601,6 +611,8 @@
      */
     @Test
     public void testPlaybackWhileMusicPlaying() {
+        when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
+                .thenReturn(false);
         Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState());
         doReturn(true).when(mAudioManager).isMusicActive();
         setUpConnectedState(true, true);
@@ -608,7 +620,6 @@
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
                 PlaybackStateCompat.STATE_PLAYING);
         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
-        verify(mAudioManager, times(1)).isMusicActive();
         verify(mAvrcpControllerService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
@@ -629,7 +640,6 @@
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
                 PlaybackStateCompat.STATE_PLAYING);
         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
-        verify(mAudioManager, times(1)).isMusicActive();
         TestUtils.waitForLooperToFinishScheduledTask(
                 A2dpSinkService.getA2dpSinkService().getMainLooper());
         Assert.assertEquals(AudioManager.AUDIOFOCUS_GAIN, A2dpSinkService.getFocusState());
diff --git a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index f33d2e0..a65dffb 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
@@ -71,6 +71,7 @@
     private static final String TAG = AdapterServiceTest.class.getSimpleName();
 
     private AdapterService mAdapterService;
+    private AdapterService.AdapterServiceBinder mServiceBinder;
 
     private @Mock Context mMockContext;
     private @Mock ApplicationInfo mMockApplicationInfo;
@@ -119,6 +120,7 @@
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(
                 () -> mAdapterService = new AdapterService());
+        mServiceBinder = new AdapterService.AdapterServiceBinder(mAdapterService);
         mMockPackageManager = mock(PackageManager.class);
         mMockContentResolver = new MockContentResolver(mMockContext);
         MockitoAnnotations.initMocks(this);
@@ -157,7 +159,7 @@
         mAdapterService.attach(mMockContext, null, null, null, null, null);
 
         mAdapterService.onCreate();
-        mAdapterService.registerCallback(mIBluetoothCallback);
+        mServiceBinder.registerCallback(mIBluetoothCallback);
 
         Config.init(mMockContext);
 
@@ -167,7 +169,7 @@
 
     @After
     public void tearDown() {
-        mAdapterService.unregisterCallback(mIBluetoothCallback);
+        mServiceBinder.unregisterCallback(mIBluetoothCallback);
         mAdapterService.cleanup();
         Config.init(InstrumentationRegistry.getTargetContext());
     }
@@ -182,11 +184,11 @@
     }
 
     private void doEnable(int invocationNumber, boolean onlyGatt) {
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
 
         final int startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2
 
-        mAdapterService.enable();
+        mAdapterService.enable(false);
 
         verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON,
                 invocationNumber + 1, CONTEXT_SWITCH_MS);
@@ -200,7 +202,7 @@
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON,
                 invocationNumber + 1, NATIVE_INIT_MS);
 
-        mAdapterService.onLeServiceUp();
+        mServiceBinder.onLeServiceUp();
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON,
                 invocationNumber + 1, CONTEXT_SWITCH_MS);
@@ -220,14 +222,14 @@
 
         verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(2 * invocationNumber + 2))
                 .sendBroadcast(any(), eq(android.Manifest.permission.BLUETOOTH));
-        final int scanMode = mAdapterService.getScanMode();
+        final int scanMode = mServiceBinder.getScanMode();
         Assert.assertTrue(scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
                 || scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
-        Assert.assertTrue(mAdapterService.isEnabled());
+        Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
     }
 
     private void doDisable(int invocationNumber, boolean onlyGatt) {
-        Assert.assertTrue(mAdapterService.isEnabled());
+        Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
 
         final int startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2
 
@@ -247,7 +249,7 @@
         verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON,
                 invocationNumber + 1, CONTEXT_SWITCH_MS);
 
-        mAdapterService.onBrEdrDown();
+        mServiceBinder.onBrEdrDown();
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF,
                 invocationNumber + 1, CONTEXT_SWITCH_MS);
@@ -260,7 +262,7 @@
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF,
                 invocationNumber + 1, NATIVE_DISABLE_MS);
 
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
     }
 
     /**
@@ -314,9 +316,9 @@
      */
     @Test
     public void testGattStartTimeout() {
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
 
-        mAdapterService.enable();
+        mAdapterService.enable(false);
 
         verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON, 1,
                 CONTEXT_SWITCH_MS);
@@ -336,7 +338,7 @@
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
                 NATIVE_DISABLE_MS);
 
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
     }
 
     /**
@@ -346,7 +348,7 @@
     @Test
     public void testGattStopTimeout() {
         doEnable(0, false);
-        Assert.assertTrue(mAdapterService.isEnabled());
+        Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
 
         mAdapterService.disable();
 
@@ -361,7 +363,7 @@
         verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, 1,
                 CONTEXT_SWITCH_MS);
 
-        mAdapterService.onBrEdrDown();
+        mServiceBinder.onBrEdrDown();
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF, 1,
                 CONTEXT_SWITCH_MS);
@@ -372,7 +374,7 @@
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
                 AdapterState.BLE_STOP_TIMEOUT_DELAY + NATIVE_DISABLE_MS);
 
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
     }
 
     /**
@@ -381,9 +383,9 @@
      */
     @Test
     public void testProfileStartTimeout() {
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
 
-        mAdapterService.enable();
+        mAdapterService.enable(false);
 
         verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON, 1,
                 CONTEXT_SWITCH_MS);
@@ -396,7 +398,7 @@
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON, 1,
                 NATIVE_INIT_MS);
 
-        mAdapterService.onLeServiceUp();
+        mServiceBinder.onLeServiceUp();
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON, 1,
                 CONTEXT_SWITCH_MS);
@@ -426,7 +428,7 @@
     public void testProfileStopTimeout() {
         doEnable(0, false);
 
-        Assert.assertTrue(mAdapterService.isEnabled());
+        Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
 
         mAdapterService.disable();
 
@@ -448,7 +450,7 @@
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
                 AdapterState.BLE_STOP_TIMEOUT_DELAY + NATIVE_DISABLE_MS);
 
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
     }
 
     /**
@@ -462,7 +464,7 @@
         SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, "false");
         doEnable(0, false);
 
-        Assert.assertTrue(mAdapterService.isEnabled());
+        Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
 
         Assert.assertFalse(
                 SystemProperties.get(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY,
@@ -495,7 +497,7 @@
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
                 NATIVE_DISABLE_MS);
 
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
 
         // Restore earlier setting
         SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, snoopSetting);
@@ -518,7 +520,7 @@
      */
     @Test
     public void testObfuscateBluetoothAddress_BluetoothDisabled() {
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
         Assert.assertNotNull(metricsSalt);
         BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
@@ -534,9 +536,9 @@
      */
     @Test
     public void testObfuscateBluetoothAddress_BluetoothEnabled() {
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         doEnable(0, false);
-        Assert.assertTrue(mAdapterService.isEnabled());
+        Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
         Assert.assertNotNull(metricsSalt);
         BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
@@ -551,7 +553,7 @@
      */
     @Test
     public void testObfuscateBluetoothAddress_PersistentBetweenToggle() {
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
         Assert.assertNotNull(metricsSalt);
         BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
@@ -562,7 +564,7 @@
                 obfuscatedAddress1);
         // Enable
         doEnable(0, false);
-        Assert.assertTrue(mAdapterService.isEnabled());
+        Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress3.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
@@ -570,7 +572,7 @@
                 obfuscatedAddress1);
         // Disable
         doDisable(0, false);
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress4.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
@@ -587,7 +589,7 @@
             PackageManager.NameNotFoundException {
         byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
         Assert.assertNotNull(metricsSalt);
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
         byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress1.length > 0);
@@ -596,7 +598,7 @@
                 obfuscatedAddress1);
         tearDown();
         setUp();
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress2.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
@@ -623,12 +625,12 @@
             + " after factory reset")
     @Test
     public void testObfuscateBluetoothAddress_FactoryReset() {
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
         byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress1.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
-        mAdapterService.factoryReset();
+        mServiceBinder.factoryReset();
         byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress2.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
@@ -640,7 +642,7 @@
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
         Assert.assertArrayEquals(obfuscatedAddress3,
                 obfuscatedAddress2);
-        mAdapterService.factoryReset();
+        mServiceBinder.factoryReset();
         byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress4.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
@@ -657,14 +659,14 @@
             PackageManager.NameNotFoundException {
         byte[] metricsSalt1 = getMetricsSalt(mAdapterConfig);
         Assert.assertNotNull(metricsSalt1);
-        Assert.assertFalse(mAdapterService.isEnabled());
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
         BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
         byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress1.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
         Assert.assertArrayEquals(obfuscateInJava(metricsSalt1, device),
                 obfuscatedAddress1);
-        mAdapterService.factoryReset();
+        mServiceBinder.factoryReset();
         tearDown();
         setUp();
         // Cannot verify metrics salt since it is not written to disk until native cleanup
diff --git a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
index dafdc5a..16f2712 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
@@ -33,6 +33,7 @@
 
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.bluetooth.hfp.HeadsetService;
 
 import org.junit.After;
@@ -44,6 +45,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -62,6 +64,7 @@
     @Mock private ServiceFactory mServiceFactory;
     @Mock private HeadsetService mHeadsetService;
     @Mock private A2dpService mA2dpService;
+    @Mock private DatabaseManager mDatabaseManager;
 
     @Before
     public void setUp() throws Exception {
@@ -103,26 +106,28 @@
     @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 HeadsetService to return unknown connection policy
+        when(mHeadsetService.getConnectionPolicy(device))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
 
-        // Mock the A2DP service to return undefined priority
-        when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        // Mock the A2DP service to return undefined unknown connection policy
+        when(mA2dpService.getConnectionPolicy(device))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
 
         // 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;
+        uuids[0] = BluetoothUuid.HFP;
+        uuids[1] = BluetoothUuid.A2DP_SINK;
         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));
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setConnectionPolicy(eq(device),
+                eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
+        verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setConnectionPolicy(eq(device),
+                eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
     }
 
     /**
@@ -137,16 +142,17 @@
         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 a list of connection order
+        BluetoothDevice bondedDevice = TestUtils.getTestDevice(mAdapter, 0);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(bondedDevice);
+        when(mAdapterService.getBondState(bondedDevice)).thenReturn(BluetoothDevice.BOND_BONDED);
 
-        // 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);
+        // Return CONNECTION_POLICY_ALLOWED over HFP and A2DP
+        when(mHeadsetService.getConnectionPolicy(bondedDevice)).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mA2dpService.getConnectionPolicy(bondedDevice)).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         // Inject an event that the adapter is turned on.
         Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
@@ -154,13 +160,12 @@
         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]));
+        verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevice));
+        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevice));
     }
 
     /**
-     * Test that when an auto connect device is disconnected, its priority is set to ON if its
-     * original priority is auto connect
+     * Test that when an active device is disconnected, we will not auto connect it
      */
     @Test
     public void testDisconnectNoAutoConnect() {
@@ -168,93 +173,85 @@
         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);
+        // Return a list of connection order
+        List<BluetoothDevice> connectionOrder = new ArrayList<>();
+        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 0));
+        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 1));
+        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 2));
+        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 3));
+
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(
+                connectionOrder.get(0));
 
         // 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);
+        when(mHeadsetService.getConnectionPolicy(connectionOrder.get(0))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mHeadsetService.getConnectionPolicy(connectionOrder.get(1))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mHeadsetService.getConnectionPolicy(connectionOrder.get(2))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mHeadsetService.getConnectionPolicy(connectionOrder.get(3))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
 
         // Make one of the device active
         Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(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);
+        // Only calls setConnection on device connectionOrder.get(0) with STATE_CONNECTED
+        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setConnection(
+                connectionOrder.get(0), true);
+        verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(1)), anyBoolean());
+        verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(2)), anyBoolean());
+        verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(3)), anyBoolean());
 
         // Make another device active
-        when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
+        when(mHeadsetService.getConnectionState(connectionOrder.get(1))).thenReturn(
                 BluetoothProfile.STATE_CONNECTED);
         intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(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);
+        // Only calls setConnection on device connectionOrder.get(1) with STATE_CONNECTED
+        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection(
+                connectionOrder.get(0), true);
+        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection(
+                connectionOrder.get(1), true);
+        verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(2)), anyBoolean());
+        verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(3)), anyBoolean());
 
-        // Set active device to null
-        when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
+        // Disconnect a2dp for the device
+        when(mHeadsetService.getConnectionState(connectionOrder.get(1))).thenReturn(
                 BluetoothProfile.STATE_DISCONNECTED);
-        intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) null);
+        intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(1));
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
 
-        // Verify that the priority of previous active device won't be changed while active device
-        // set to null
-        verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setPriority(
-                bondedDevices[1], BluetoothProfile.PRIORITY_ON);
-        verify(mHeadsetService).setPriority(bondedDevices[1],
-                BluetoothProfile.PRIORITY_AUTO_CONNECT);
-        verify(mHeadsetService, never()).setPriority(bondedDevices[1],
-                BluetoothProfile.PRIORITY_OFF);
+        // Verify that we do not call setConnection, but instead setDisconnection on disconnect
+        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection(
+                connectionOrder.get(1), true);
+        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setDisconnection(
+                connectionOrder.get(1));
 
         // Make the current active device fail to connect
-        when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
+        when(mA2dpService.getConnectionState(connectionOrder.get(1))).thenReturn(
                 BluetoothProfile.STATE_DISCONNECTED);
-        updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET,
+        updateProfileConnectionStateHelper(connectionOrder.get(1), BluetoothProfile.HEADSET,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
 
-        // This device should be set to ON
-        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
-                bondedDevices[1], BluetoothProfile.PRIORITY_ON);
+        // Verify we don't call deleteConnection as that only happens when we disconnect a2dp
+        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setDisconnection(
+                connectionOrder.get(1));
 
-        // Verify that we are not setting priorities to random devices and values
-        verify(mHeadsetService, times(7)).setPriority(any(BluetoothDevice.class), anyInt());
+        // Verify we didn't have any unexpected calls to setConnection or deleteConnection
+        verify(mDatabaseManager, times(2)).setConnection(any(BluetoothDevice.class), anyBoolean());
+        verify(mDatabaseManager, times(1)).setDisconnection(any(BluetoothDevice.class));
     }
 
     /**
@@ -270,10 +267,10 @@
 
         // 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.getConnectionPolicy(bondedDevices[0])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
 
@@ -304,17 +301,18 @@
      */
     @Test
     public void testReconnectOnPartialConnect_PreviousPartialFail() {
-        // Return a list of bonded devices (just one)
-        BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
-        bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
-        when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
+        List<BluetoothDevice> connectionOrder = new ArrayList<>();
+        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 0));
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(
+                connectionOrder.get(0));
 
         // 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.getConnectionPolicy(connectionOrder.get(0))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mA2dpService.getConnectionPolicy(connectionOrder.get(0))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
 
@@ -322,54 +320,54 @@
         // To enable that we need to make sure that HeadsetService returns the device among a list
         // of connected devices
         ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
-        hsConnectedDevices.add(bondedDevices[0]);
+        hsConnectedDevices.add(connectionOrder.get(0));
         when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
         // Also the A2DP should say that its not connected for same device
-        when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
+        when(mA2dpService.getConnectionState(connectionOrder.get(0))).thenReturn(
                 BluetoothProfile.STATE_DISCONNECTED);
 
         // We send a connection success event for one profile since the re-connect *only* works if
         // we have already connected successfully over one of the profiles
-        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+        updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET,
                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
 
         // Check that we get a call to A2DP reconnect
         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
-                bondedDevices[0]);
+                connectionOrder.get(0));
 
         // We send a connection failure event for the attempted profile, and keep the connected
         // profile connected.
-        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.A2DP,
+        updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.A2DP,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
 
         TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
 
         // Verify no one changes the priority of the failed profile
-        verify(mA2dpService, never()).setPriority(eq(bondedDevices[0]), anyInt());
+        verify(mA2dpService, never()).setConnectionPolicy(eq(connectionOrder.get(0)), anyInt());
 
         // Send a connection success event for one profile again without disconnecting all profiles
-        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+        updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET,
                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
 
         // Check that we won't get a call to A2DP reconnect again before all profiles disconnected
         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
-                bondedDevices[0]);
+                connectionOrder.get(0));
 
         // Send a disconnection event for all connected profiles
-        hsConnectedDevices.remove(bondedDevices[0]);
-        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+        hsConnectedDevices.remove(connectionOrder.get(0));
+        updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);
 
         TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
 
         // Send a connection success event for one profile again to trigger re-connect
-        hsConnectedDevices.add(bondedDevices[0]);
-        updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+        hsConnectedDevices.add(connectionOrder.get(0));
+        updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET,
                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
 
         // Check that we get a call to A2DP connect again
         verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).times(2)).connect(
-                bondedDevices[0]);
+                connectionOrder.get(0));
     }
 
     /**
@@ -394,10 +392,10 @@
 
             // 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);
+            when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            when(mA2dpService.getConnectionPolicy(testDevice)).thenReturn(
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             // 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.
@@ -438,15 +436,15 @@
     }
 
     /**
-     * Test that the connect priority of all devices are set as appropriate if there is one
+     * Test that the connection policy 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
+     *   BluetoothProfile.CONNECTION_POLICY_ALLOWED
      */
     @Test
-    public void testSetPriorityMultipleDevices() {
+    public void testSetConnectionPolicyMultipleDevices() {
         // 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
@@ -465,28 +463,31 @@
             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);
+                when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+                when(mA2dpService.getConnectionPolicy(testDevice)).thenReturn(
+                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             }
             if (i == 1) {
                 hsConnectedDevices.add(testDevice);
-                when(mHeadsetService.getPriority(testDevice)).thenReturn(
-                        BluetoothProfile.PRIORITY_ON);
-                when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
+                when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+                when(mA2dpService.getConnectionPolicy(testDevice))
+                        .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             }
             if (i == 2) {
                 a2dpConnectedDevices.add(testDevice);
-                when(mHeadsetService.getPriority(testDevice)).thenReturn(
-                        BluetoothProfile.PRIORITY_ON);
-                when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
+                when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+                when(mA2dpService.getConnectionPolicy(testDevice))
+                        .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             }
             if (i == 3) {
                 // Device not connected
-                when(mHeadsetService.getPriority(testDevice)).thenReturn(
-                        BluetoothProfile.PRIORITY_ON);
-                when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
+                when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+                when(mA2dpService.getConnectionPolicy(testDevice))
+                        .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             }
         }
         when(mAdapterService.getBondedDevices()).thenReturn(testDevices);
@@ -532,21 +533,22 @@
                 BluetoothProfile.STATE_CONNECTED);
 
         // Check the connect priorities for all devices
-        // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called
+        // - testDevices[0] - connected for HFP and A2DP: setConnectionPolicy() 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]),
+        // - testDevices[2] - connected for A2DP: setConnectionPolicy() should not be called
+        // - testDevices[3] - not connected for HFP nor A2DP: setConnectionPolicy() should not be
+        //                    called
+        verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt());
+        verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt());
+        verify(mHeadsetService, times(0)).setConnectionPolicy(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());
+        verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[1]), anyInt());
+        verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[2]), anyInt());
+        verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[2]), anyInt());
+        verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt());
+        verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt());
         clearInvocations(mHeadsetService, mA2dpService);
 
         // Generate connection state changed for A2DP for testDevices[2] and trigger
@@ -565,21 +567,22 @@
                 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[0] - connected for HFP and A2DP: setConnectionPolicy() should not be called
+        // - testDevices[1] - connected for HFP and A2DP: setConnectionPolicy() 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]),
+        // - testDevices[3] - not connected for HFP nor A2DP: setConnectionPolicy() should not be
+        //                    called
+        verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt());
+        verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt());
+        verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[1]), anyInt());
+        verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[1]), anyInt());
+        verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[2]), anyInt());
+        verify(mA2dpService, times(0)).setConnectionPolicy(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());
+        verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt());
+        verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt());
         clearInvocations(mHeadsetService, mA2dpService);
     }
 
@@ -595,10 +598,10 @@
 
         // 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.getConnectionPolicy(bondedDevices[0])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
 
@@ -641,14 +644,14 @@
 
         // 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(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mHeadsetService.getConnectionPolicy(bondedDevices[1])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mA2dpService.getConnectionPolicy(bondedDevices[1])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
 
@@ -696,14 +699,14 @@
 
         // 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(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mHeadsetService.getConnectionPolicy(bondedDevices[1])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mA2dpService.getConnectionPolicy(bondedDevices[1])).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
 
@@ -738,10 +741,12 @@
     public void testNoSupportedUuids() {
         // Mock the HeadsetService to return undefined priority
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
-        when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        when(mHeadsetService.getConnectionPolicy(device))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
 
         // Mock the A2DP service to return undefined priority
-        when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        when(mA2dpService.getConnectionPolicy(device))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
 
         // Inject an event for UUIDs updated for a remote device with only HFP enabled
         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
@@ -752,9 +757,10 @@
 
         // 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));
+                after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never())
+                .setConnectionPolicy(eq(device), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
+        verify(mA2dpService, never())
+                .setConnectionPolicy(eq(device), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
     }
 
     private void updateProfileConnectionStateHelper(BluetoothDevice device, int profileId,
diff --git a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index f1d9d16..81abfc6 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -270,8 +270,6 @@
         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
         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());
@@ -294,8 +292,6 @@
                 mStringArgument.capture());
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
         Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
-
-        verifyNoMoreInteractions(mAdapterService);
     }
 
     @Test
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
index 2cb3630..0c71659 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
@@ -16,7 +16,15 @@
 
 package com.android.bluetooth.btservice.storage;
 
-import static org.mockito.Mockito.*;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
@@ -60,9 +68,12 @@
     private DatabaseManager mDatabaseManager;
     private BluetoothDevice mTestDevice;
     private BluetoothDevice mTestDevice2;
+    private BluetoothDevice mTestDevice3;
 
     private static final String LOCAL_STORAGE = "LocalStorage";
     private static final String TEST_BT_ADDR = "11:22:33:44:55:66";
+    private static final String TEST_BT_ADDR2 = "66:55:44:33:22:11";
+    private static final String TEST_BT_ADDR3 = "12:34:56:65:43:21";
     private static final String OTHER_BT_ADDR1 = "11:11:11:11:11:11";
     private static final String OTHER_BT_ADDR2 = "22:22:22:22:22:22";
     private static final String DB_NAME = "test_db";
@@ -82,6 +93,8 @@
         TestUtils.setAdapterService(mAdapterService);
 
         mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR);
+        mTestDevice2 = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR2);
+        mTestDevice3 = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR3);
 
         // Create a memory database for DatabaseManager instead of use a real database.
         mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
@@ -113,8 +126,8 @@
         restartDatabaseManagerHelper();
 
         for (int id = 0; id < BluetoothProfile.MAX_PROFILE_ID; id++) {
-            Assert.assertEquals(BluetoothProfile.PRIORITY_UNDEFINED,
-                    mDatabaseManager.getProfilePriority(mTestDevice, id));
+            Assert.assertEquals(BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+                    mDatabaseManager.getProfileConnectionPolicy(mTestDevice, id));
         }
 
         Assert.assertEquals(BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
@@ -126,35 +139,36 @@
         for (int id = 0; id < MAX_META_ID; id++) {
             Assert.assertNull(mDatabaseManager.getCustomMeta(mTestDevice, id));
         }
+
+        mDatabaseManager.factoryReset();
+        mDatabaseManager.mMetadataCache.clear();
+        // Wait for clear database
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
     }
 
     @Test
-    public void testSetGetProfilePriority() {
-        int badPriority = -100;
+    public void testSetGetProfileConnectionPolicy() {
+        int badConnectionPolicy = -100;
 
         // Cases of device not in database
-        testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_UNDEFINED,
-                BluetoothProfile.PRIORITY_UNDEFINED, true);
-        testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_OFF,
-                BluetoothProfile.PRIORITY_OFF, true);
-        testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_ON,
-                BluetoothProfile.PRIORITY_ON, true);
-        testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_AUTO_CONNECT,
-                BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
-        testSetGetProfilePriorityCase(false, badPriority,
-                BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testSetGetProfileConnectionPolicyCase(false, BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
+        testSetGetProfileConnectionPolicyCase(false, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, true);
+        testSetGetProfileConnectionPolicyCase(false, BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
+        testSetGetProfileConnectionPolicyCase(false, badConnectionPolicy,
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
 
         // Cases of device already in database
-        testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_UNDEFINED,
-                BluetoothProfile.PRIORITY_UNDEFINED, true);
-        testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_OFF,
-                BluetoothProfile.PRIORITY_OFF, true);
-        testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_ON,
-                BluetoothProfile.PRIORITY_ON, true);
-        testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_AUTO_CONNECT,
-                BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
-        testSetGetProfilePriorityCase(true, badPriority,
-                BluetoothProfile.PRIORITY_UNDEFINED, false);
+        testSetGetProfileConnectionPolicyCase(true, BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
+        testSetGetProfileConnectionPolicyCase(true, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, true);
+        testSetGetProfileConnectionPolicyCase(true, BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
+        testSetGetProfileConnectionPolicyCase(true, badConnectionPolicy,
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
     }
 
     @Test
@@ -249,10 +263,10 @@
         Metadata checkData = list.get(0);
         Assert.assertEquals(TEST_BT_ADDR, checkData.getAddress());
 
-        mDatabase.deleteAll();
+        mDatabaseManager.factoryReset();
+        mDatabaseManager.mMetadataCache.clear();
         // Wait for clear database
         TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
-        mDatabaseManager.mMetadataCache.clear();
     }
 
     @Test
@@ -299,14 +313,14 @@
 
         // Check whether the devices are in the database
         Metadata checkData1 = list.get(0);
-        Assert.assertEquals(OTHER_BT_ADDR1, checkData1.getAddress());
+        Assert.assertEquals(OTHER_BT_ADDR2, checkData1.getAddress());
         Metadata checkData2 = list.get(1);
-        Assert.assertEquals(OTHER_BT_ADDR2, checkData2.getAddress());
+        Assert.assertEquals(OTHER_BT_ADDR1, checkData2.getAddress());
 
-        mDatabase.deleteAll();
+        mDatabaseManager.factoryReset();
+        mDatabaseManager.mMetadataCache.clear();
         // Wait for clear database
         TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
-        mDatabaseManager.mMetadataCache.clear();
 
     }
 
@@ -390,6 +404,176 @@
     }
 
     @Test
+    public void testSetConnection() {
+        // Verify pre-conditions to ensure a fresh test
+        Assert.assertEquals(0, mDatabaseManager.mMetadataCache.size());
+        Assert.assertNotNull(mTestDevice);
+        Assert.assertNotNull(mTestDevice2);
+        Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+
+        // Set the first device's connection
+        mDatabaseManager.setConnection(mTestDevice, true);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertTrue(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        List<BluetoothDevice> mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        Assert.assertEquals(1, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
+
+        // Setting the second device's connection
+        mDatabaseManager.setConnection(mTestDevice2, true);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        Assert.assertTrue(mDatabaseManager
+                .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+        Assert.assertEquals(mTestDevice2, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(0));
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+
+        // Connect first test device again
+        mDatabaseManager.setConnection(mTestDevice, true);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertTrue(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+        Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
+        Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(1));
+
+        // Disconnect first test device's connection
+        mDatabaseManager.setDisconnection(mTestDevice);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+        Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
+        Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(1));
+
+        // Connect third test device (non-a2dp device)
+        mDatabaseManager.setConnection(mTestDevice3, false);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+        Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+        Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+        // Connect first test device again
+        mDatabaseManager.setConnection(mTestDevice, true);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertTrue(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+        Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
+        Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(1));
+        Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+        // Connect third test device again and ensure it doesn't reset active a2dp device
+        mDatabaseManager.setConnection(mTestDevice3, false);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertTrue(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+        Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+        Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+        // Disconnect second test device
+        mDatabaseManager.setDisconnection(mTestDevice2);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertTrue(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+        Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+        Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+        // Disconnect first test device
+        mDatabaseManager.setDisconnection(mTestDevice);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+        Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+        Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+        // Disconnect third test device
+        mDatabaseManager.setDisconnection(mTestDevice3);
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+        Assert.assertFalse(mDatabaseManager
+                .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+        Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+        Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+        Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+        Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+        mDatabaseManager.factoryReset();
+        mDatabaseManager.mMetadataCache.clear();
+        // Wait for clear database
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+    }
+
+    @Test
     public void testDatabaseMigration_100_101() throws IOException {
         // Create a database with version 100
         SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 100);
@@ -450,7 +634,7 @@
         device.put("unthethered_left_charging", testString);
         device.put("unthethered_right_charging", testString);
         device.put("unthethered_case_charging", testString);
-        Assert.assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+        assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
                 CoreMatchers.not(-1));
 
         // Check the metadata names on version 101
@@ -533,22 +717,234 @@
         }
     }
 
+    @Test
+    public void testDatabaseMigration_102_103() throws IOException {
+        String testString = "TEST STRING";
+
+        // Create a database with version 102
+        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 102);
+        Cursor cursor = db.query("SELECT * FROM metadata");
+
+        // insert a device to the database
+        ContentValues device = new ContentValues();
+        device.put("address", TEST_BT_ADDR);
+        device.put("migrated", false);
+        device.put("a2dpSupportsOptionalCodecs", -1);
+        device.put("a2dpOptionalCodecsEnabled", -1);
+        device.put("a2dp_priority", 1000);
+        device.put("a2dp_sink_priority", 1000);
+        device.put("hfp_priority", 1000);
+        device.put("hfp_client_priority", 1000);
+        device.put("hid_host_priority", 1000);
+        device.put("pan_priority", 1000);
+        device.put("pbap_priority", 1000);
+        device.put("pbap_client_priority", 1000);
+        device.put("map_priority", 1000);
+        device.put("sap_priority", 1000);
+        device.put("hearing_aid_priority", 1000);
+        device.put("map_client_priority", 1000);
+        device.put("manufacturer_name", testString);
+        device.put("model_name", testString);
+        device.put("software_version", testString);
+        device.put("hardware_version", testString);
+        device.put("companion_app", testString);
+        device.put("main_icon", testString);
+        device.put("is_untethered_headset", testString);
+        device.put("untethered_left_icon", testString);
+        device.put("untethered_right_icon", testString);
+        device.put("untethered_case_icon", testString);
+        device.put("untethered_left_battery", testString);
+        device.put("untethered_right_battery", testString);
+        device.put("untethered_case_battery", testString);
+        device.put("untethered_left_charging", testString);
+        device.put("untethered_right_charging", testString);
+        device.put("untethered_case_charging", testString);
+        assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+                CoreMatchers.not(-1));
+
+        // Check the metadata names on version 102
+        assertHasColumn(cursor, "a2dp_priority", true);
+        assertHasColumn(cursor, "a2dp_sink_priority", true);
+        assertHasColumn(cursor, "hfp_priority", true);
+        assertHasColumn(cursor, "hfp_client_priority", true);
+        assertHasColumn(cursor, "hid_host_priority", true);
+        assertHasColumn(cursor, "pan_priority", true);
+        assertHasColumn(cursor, "pbap_priority", true);
+        assertHasColumn(cursor, "pbap_client_priority", true);
+        assertHasColumn(cursor, "map_priority", true);
+        assertHasColumn(cursor, "sap_priority", true);
+        assertHasColumn(cursor, "hearing_aid_priority", true);
+        assertHasColumn(cursor, "map_client_priority", true);
+
+        // Migrate database from 102 to 103
+        db.close();
+        db = testHelper.runMigrationsAndValidate(DB_NAME, 103, true,
+                MetadataDatabase.MIGRATION_102_103);
+        cursor = db.query("SELECT * FROM metadata");
+
+        // metadata names should be changed on version 103
+        assertHasColumn(cursor, "a2dp_priority", false);
+        assertHasColumn(cursor, "a2dp_sink_priority", false);
+        assertHasColumn(cursor, "hfp_priority", false);
+        assertHasColumn(cursor, "hfp_client_priority", false);
+        assertHasColumn(cursor, "hid_host_priority", false);
+        assertHasColumn(cursor, "pan_priority", false);
+        assertHasColumn(cursor, "pbap_priority", false);
+        assertHasColumn(cursor, "pbap_client_priority", false);
+        assertHasColumn(cursor, "map_priority", false);
+        assertHasColumn(cursor, "sap_priority", false);
+        assertHasColumn(cursor, "hearing_aid_priority", false);
+        assertHasColumn(cursor, "map_client_priority", false);
+
+        assertHasColumn(cursor, "a2dp_connection_policy", true);
+        assertHasColumn(cursor, "a2dp_sink_connection_policy", true);
+        assertHasColumn(cursor, "hfp_connection_policy", true);
+        assertHasColumn(cursor, "hfp_client_connection_policy", true);
+        assertHasColumn(cursor, "hid_host_connection_policy", true);
+        assertHasColumn(cursor, "pan_connection_policy", true);
+        assertHasColumn(cursor, "pbap_connection_policy", true);
+        assertHasColumn(cursor, "pbap_client_connection_policy", true);
+        assertHasColumn(cursor, "map_connection_policy", true);
+        assertHasColumn(cursor, "sap_connection_policy", true);
+        assertHasColumn(cursor, "hearing_aid_connection_policy", true);
+        assertHasColumn(cursor, "map_client_connection_policy", true);
+
+        while (cursor.moveToNext()) {
+            // Check PRIORITY_AUTO_CONNECT (1000) was replaced with CONNECTION_POLICY_ALLOWED (100)
+            assertColumnIntData(cursor, "a2dp_connection_policy", 100);
+            assertColumnIntData(cursor, "a2dp_sink_connection_policy", 100);
+            assertColumnIntData(cursor, "hfp_connection_policy", 100);
+            assertColumnIntData(cursor, "hfp_client_connection_policy", 100);
+            assertColumnIntData(cursor, "hid_host_connection_policy", 100);
+            assertColumnIntData(cursor, "pan_connection_policy", 100);
+            assertColumnIntData(cursor, "pbap_connection_policy", 100);
+            assertColumnIntData(cursor, "pbap_client_connection_policy", 100);
+            assertColumnIntData(cursor, "map_connection_policy", 100);
+            assertColumnIntData(cursor, "sap_connection_policy", 100);
+            assertColumnIntData(cursor, "hearing_aid_connection_policy", 100);
+            assertColumnIntData(cursor, "map_client_connection_policy", 100);
+
+            // Check whether metadata data type are blob
+            assertColumnBlob(cursor, "manufacturer_name");
+            assertColumnBlob(cursor, "model_name");
+            assertColumnBlob(cursor, "software_version");
+            assertColumnBlob(cursor, "hardware_version");
+            assertColumnBlob(cursor, "companion_app");
+            assertColumnBlob(cursor, "main_icon");
+            assertColumnBlob(cursor, "is_untethered_headset");
+            assertColumnBlob(cursor, "untethered_left_icon");
+            assertColumnBlob(cursor, "untethered_right_icon");
+            assertColumnBlob(cursor, "untethered_case_icon");
+            assertColumnBlob(cursor, "untethered_left_battery");
+            assertColumnBlob(cursor, "untethered_right_battery");
+            assertColumnBlob(cursor, "untethered_case_battery");
+            assertColumnBlob(cursor, "untethered_left_charging");
+            assertColumnBlob(cursor, "untethered_right_charging");
+            assertColumnBlob(cursor, "untethered_case_charging");
+
+            // Check whether metadata values are migrated to version 103 successfully
+            assertColumnBlobData(cursor, "manufacturer_name", testString.getBytes());
+            assertColumnBlobData(cursor, "model_name", testString.getBytes());
+            assertColumnBlobData(cursor, "software_version", testString.getBytes());
+            assertColumnBlobData(cursor, "hardware_version", testString.getBytes());
+            assertColumnBlobData(cursor, "companion_app", testString.getBytes());
+            assertColumnBlobData(cursor, "main_icon", testString.getBytes());
+            assertColumnBlobData(cursor, "is_untethered_headset", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_left_icon", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_right_icon", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_case_icon", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_left_battery", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_right_battery", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_case_battery", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_left_charging", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_right_charging", testString.getBytes());
+            assertColumnBlobData(cursor, "untethered_case_charging", testString.getBytes());
+        }
+    }
+
+    @Test
+    public void testDatabaseMigration_103_104() throws IOException {
+        String testString = "TEST STRING";
+
+        // Create a database with version 103
+        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 103);
+
+        // insert a device to the database
+        ContentValues device = new ContentValues();
+        device.put("address", TEST_BT_ADDR);
+        device.put("migrated", false);
+        device.put("a2dpSupportsOptionalCodecs", -1);
+        device.put("a2dpOptionalCodecsEnabled", -1);
+        device.put("a2dp_connection_policy", 100);
+        device.put("a2dp_sink_connection_policy", 100);
+        device.put("hfp_connection_policy", 100);
+        device.put("hfp_client_connection_policy", 100);
+        device.put("hid_host_connection_policy", 100);
+        device.put("pan_connection_policy", 100);
+        device.put("pbap_connection_policy", 100);
+        device.put("pbap_client_connection_policy", 100);
+        device.put("map_connection_policy", 100);
+        device.put("sap_connection_policy", 100);
+        device.put("hearing_aid_connection_policy", 100);
+        device.put("map_client_connection_policy", 100);
+        device.put("manufacturer_name", testString);
+        device.put("model_name", testString);
+        device.put("software_version", testString);
+        device.put("hardware_version", testString);
+        device.put("companion_app", testString);
+        device.put("main_icon", testString);
+        device.put("is_untethered_headset", testString);
+        device.put("untethered_left_icon", testString);
+        device.put("untethered_right_icon", testString);
+        device.put("untethered_case_icon", testString);
+        device.put("untethered_left_battery", testString);
+        device.put("untethered_right_battery", testString);
+        device.put("untethered_case_battery", testString);
+        device.put("untethered_left_charging", testString);
+        device.put("untethered_right_charging", testString);
+        device.put("untethered_case_charging", testString);
+        assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+                CoreMatchers.not(-1));
+
+        // Migrate database from 103 to 104
+        db.close();
+        db = testHelper.runMigrationsAndValidate(DB_NAME, 104, true,
+                MetadataDatabase.MIGRATION_103_104);
+        Cursor cursor = db.query("SELECT * FROM metadata");
+
+        assertHasColumn(cursor, "last_active_time", true);
+        assertHasColumn(cursor, "is_active_a2dp_device", true);
+
+        while (cursor.moveToNext()) {
+            // Check the two new columns were added with their default values
+            assertColumnIntData(cursor, "last_active_time", -1);
+            assertColumnIntData(cursor, "is_active_a2dp_device", 0);
+        }
+    }
+
     /**
      * Helper function to check whether the database has the expected column
      */
     void assertHasColumn(Cursor cursor, String columnName, boolean hasColumn) {
         if (hasColumn) {
-            Assert.assertThat(cursor.getColumnIndex(columnName), CoreMatchers.not(-1));
+            assertThat(cursor.getColumnIndex(columnName), CoreMatchers.not(-1));
         } else {
-            Assert.assertThat(cursor.getColumnIndex(columnName), CoreMatchers.is(-1));
+            assertThat(cursor.getColumnIndex(columnName), CoreMatchers.is(-1));
         }
     }
 
     /**
+     * Helper function to check whether the database has the expected value
+     */
+    void assertColumnIntData(Cursor cursor, String columnName, int value) {
+        assertThat(cursor.getInt(cursor.getColumnIndex(columnName)), CoreMatchers.is(value));
+    }
+
+    /**
      * Helper function to check whether the column data type is BLOB
      */
     void assertColumnBlob(Cursor cursor, String columnName) {
-        Assert.assertThat(cursor.getType(cursor.getColumnIndex(columnName)),
+        assertThat(cursor.getType(cursor.getColumnIndex(columnName)),
                 CoreMatchers.is(Cursor.FIELD_TYPE_BLOB));
     }
 
@@ -556,7 +952,7 @@
      * Helper function to check the BLOB data in a column is expected
      */
     void assertColumnBlobData(Cursor cursor, String columnName, byte[] data) {
-        Assert.assertThat(cursor.getBlob(cursor.getColumnIndex(columnName)),
+        assertThat(cursor.getBlob(cursor.getColumnIndex(columnName)),
                 CoreMatchers.is(data));
     }
 
@@ -572,21 +968,23 @@
 
         // Remove local storage
         mDatabaseManager.mMetadataCache.remove(LOCAL_STORAGE);
-        mDatabase.delete(LOCAL_STORAGE);
+        mDatabaseManager.deleteDatabase(data);
+        // Wait for handler thread finish its task.
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
     }
 
-    void testSetGetProfilePriorityCase(boolean stored, int priority, int expectedPriority,
-            boolean expectedSetResult) {
+    void testSetGetProfileConnectionPolicyCase(boolean stored, int connectionPolicy,
+            int expectedConnectionPolicy, boolean expectedSetResult) {
         if (stored) {
             Metadata data = new Metadata(TEST_BT_ADDR);
             mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
             mDatabase.insert(data);
         }
         Assert.assertEquals(expectedSetResult,
-                mDatabaseManager.setProfilePriority(mTestDevice,
-                BluetoothProfile.HEADSET, priority));
-        Assert.assertEquals(expectedPriority,
-                mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.HEADSET));
+                mDatabaseManager.setProfileConnectionPolicy(mTestDevice,
+                BluetoothProfile.HEADSET, connectionPolicy));
+        Assert.assertEquals(expectedConnectionPolicy,
+                mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.HEADSET));
         // Wait for database update
         TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
 
@@ -594,9 +992,8 @@
 
         // Check number of metadata in the database
         if (!stored) {
-            if (priority != BluetoothProfile.PRIORITY_OFF
-                    && priority != BluetoothProfile.PRIORITY_ON
-                    && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                 // Database won't be updated
                 Assert.assertEquals(0, list.size());
                 return;
@@ -606,13 +1003,13 @@
 
         // Check whether the device is in database
         restartDatabaseManagerHelper();
-        Assert.assertEquals(expectedPriority,
-                mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.HEADSET));
+        Assert.assertEquals(expectedConnectionPolicy,
+                mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.HEADSET));
 
-        mDatabase.deleteAll();
+        mDatabaseManager.factoryReset();
+        mDatabaseManager.mMetadataCache.clear();
         // Wait for clear database
         TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
-        mDatabaseManager.mMetadataCache.clear();
     }
 
     void testSetGetA2dpOptionalCodecsCase(int test, boolean stored, int value, int expectedValue) {
@@ -653,10 +1050,10 @@
                     mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice));
         }
 
-        mDatabase.deleteAll();
+        mDatabaseManager.factoryReset();
+        mDatabaseManager.mMetadataCache.clear();
         // Wait for clear database
         TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
-        mDatabaseManager.mMetadataCache.clear();
     }
 
     void testSetGetCustomMetaCase(boolean stored, int key, byte[] value, boolean expectedResult) {
@@ -690,9 +1087,9 @@
         Assert.assertArrayEquals(value,
                 mDatabaseManager.getCustomMeta(mTestDevice, key));
 
-        mDatabase.deleteAll();
+        mDatabaseManager.factoryReset();
+        mDatabaseManager.mMetadataCache.clear();
         // Wait for clear database
         TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
-        mDatabaseManager.mMetadataCache.clear();
     }
 }
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/103.json b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/103.json
new file mode 100644
index 0000000..dd0a641
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/103.json
@@ -0,0 +1,226 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 103,
+    "identityHash": "2feccd539474eca8e3dec65a98c8b084",
+    "entities": [
+      {
+        "tableName": "metadata",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `a2dp_connection_policy` INTEGER, `a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, `hfp_client_connection_policy` INTEGER, `hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, `pbap_connection_policy` INTEGER, `pbap_client_connection_policy` INTEGER, `map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, `hearing_aid_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, `untethered_left_icon` BLOB, `untethered_right_icon` BLOB, `untethered_case_icon` BLOB, `untethered_left_battery` BLOB, `untethered_right_battery` BLOB, `untethered_case_battery` BLOB, `untethered_left_charging` BLOB, `untethered_right_charging` BLOB, `untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, PRIMARY KEY(`address`))",
+        "fields": [
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "migrated",
+            "columnName": "migrated",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpSupportsOptionalCodecs",
+            "columnName": "a2dpSupportsOptionalCodecs",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpOptionalCodecsEnabled",
+            "columnName": "a2dpOptionalCodecsEnabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.a2dp_connection_policy",
+            "columnName": "a2dp_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.a2dp_sink_connection_policy",
+            "columnName": "a2dp_sink_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hfp_connection_policy",
+            "columnName": "hfp_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hfp_client_connection_policy",
+            "columnName": "hfp_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hid_host_connection_policy",
+            "columnName": "hid_host_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pan_connection_policy",
+            "columnName": "pan_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pbap_connection_policy",
+            "columnName": "pbap_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pbap_client_connection_policy",
+            "columnName": "pbap_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.map_connection_policy",
+            "columnName": "map_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.sap_connection_policy",
+            "columnName": "sap_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hearing_aid_connection_policy",
+            "columnName": "hearing_aid_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.map_client_connection_policy",
+            "columnName": "map_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.manufacturer_name",
+            "columnName": "manufacturer_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.model_name",
+            "columnName": "model_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.software_version",
+            "columnName": "software_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.hardware_version",
+            "columnName": "hardware_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.companion_app",
+            "columnName": "companion_app",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_icon",
+            "columnName": "main_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.is_untethered_headset",
+            "columnName": "is_untethered_headset",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_icon",
+            "columnName": "untethered_left_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_icon",
+            "columnName": "untethered_right_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_icon",
+            "columnName": "untethered_case_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_battery",
+            "columnName": "untethered_left_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_battery",
+            "columnName": "untethered_right_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_battery",
+            "columnName": "untethered_case_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_charging",
+            "columnName": "untethered_left_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_charging",
+            "columnName": "untethered_right_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_charging",
+            "columnName": "untethered_case_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+            "columnName": "enhanced_settings_ui_uri",
+            "affinity": "BLOB",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "address"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2feccd539474eca8e3dec65a98c8b084')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/104.json b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/104.json
new file mode 100644
index 0000000..52c27ed
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/104.json
@@ -0,0 +1,238 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 104,
+    "identityHash": "d944677aac93d61a462f5324f4d71207",
+    "entities": [
+      {
+        "tableName": "metadata",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `last_active_time` INTEGER NOT NULL, `is_active_a2dp_device` INTEGER NOT NULL, `a2dp_connection_policy` INTEGER, `a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, `hfp_client_connection_policy` INTEGER, `hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, `pbap_connection_policy` INTEGER, `pbap_client_connection_policy` INTEGER, `map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, `hearing_aid_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, `untethered_left_icon` BLOB, `untethered_right_icon` BLOB, `untethered_case_icon` BLOB, `untethered_left_battery` BLOB, `untethered_right_battery` BLOB, `untethered_case_battery` BLOB, `untethered_left_charging` BLOB, `untethered_right_charging` BLOB, `untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, PRIMARY KEY(`address`))",
+        "fields": [
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "migrated",
+            "columnName": "migrated",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpSupportsOptionalCodecs",
+            "columnName": "a2dpSupportsOptionalCodecs",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpOptionalCodecsEnabled",
+            "columnName": "a2dpOptionalCodecsEnabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "last_active_time",
+            "columnName": "last_active_time",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "is_active_a2dp_device",
+            "columnName": "is_active_a2dp_device",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.a2dp_connection_policy",
+            "columnName": "a2dp_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.a2dp_sink_connection_policy",
+            "columnName": "a2dp_sink_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hfp_connection_policy",
+            "columnName": "hfp_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hfp_client_connection_policy",
+            "columnName": "hfp_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hid_host_connection_policy",
+            "columnName": "hid_host_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pan_connection_policy",
+            "columnName": "pan_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pbap_connection_policy",
+            "columnName": "pbap_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pbap_client_connection_policy",
+            "columnName": "pbap_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.map_connection_policy",
+            "columnName": "map_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.sap_connection_policy",
+            "columnName": "sap_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hearing_aid_connection_policy",
+            "columnName": "hearing_aid_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.map_client_connection_policy",
+            "columnName": "map_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.manufacturer_name",
+            "columnName": "manufacturer_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.model_name",
+            "columnName": "model_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.software_version",
+            "columnName": "software_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.hardware_version",
+            "columnName": "hardware_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.companion_app",
+            "columnName": "companion_app",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_icon",
+            "columnName": "main_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.is_untethered_headset",
+            "columnName": "is_untethered_headset",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_icon",
+            "columnName": "untethered_left_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_icon",
+            "columnName": "untethered_right_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_icon",
+            "columnName": "untethered_case_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_battery",
+            "columnName": "untethered_left_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_battery",
+            "columnName": "untethered_right_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_battery",
+            "columnName": "untethered_case_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_charging",
+            "columnName": "untethered_left_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_charging",
+            "columnName": "untethered_right_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_charging",
+            "columnName": "untethered_case_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+            "columnName": "enhanced_settings_ui_uri",
+            "affinity": "BLOB",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "address"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd944677aac93d61a462f5324f4d71207')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index f5f9e76..a1c938c 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -117,7 +117,7 @@
         mDeviceQueueMap.put(mSingleDevice, new LinkedBlockingQueue<>());
         doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
                 .getBondState(any(BluetoothDevice.class));
-        doReturn(new ParcelUuid[]{BluetoothUuid.HearingAid}).when(mAdapterService)
+        doReturn(new ParcelUuid[]{BluetoothUuid.HEARING_AID}).when(mAdapterService)
                 .getRemoteUuids(any(BluetoothDevice.class));
     }
 
@@ -217,32 +217,25 @@
     @Test
     public void testGetSetPriority() {
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         Assert.assertEquals("Initial device priority",
-                BluetoothProfile.PRIORITY_UNDEFINED,
-                mService.getPriority(mLeftDevice));
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+                mService.getConnectionPolicy(mLeftDevice));
 
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         Assert.assertEquals("Setting device priority to PRIORITY_OFF",
-                BluetoothProfile.PRIORITY_OFF,
-                mService.getPriority(mLeftDevice));
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+                mService.getConnectionPolicy(mLeftDevice));
 
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         Assert.assertEquals("Setting device priority to PRIORITY_ON",
-                BluetoothProfile.PRIORITY_ON,
-                mService.getPriority(mLeftDevice));
-
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
-        Assert.assertEquals("Setting device priority to PRIORITY_AUTO_CONNECT",
-                BluetoothProfile.PRIORITY_AUTO_CONNECT,
-                mService.getPriority(mLeftDevice));
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+                mService.getConnectionPolicy(mLeftDevice));
     }
 
     /**
@@ -253,43 +246,35 @@
         int badPriorityValue = 1024;
         int badBondState = 42;
         testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
-        testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mSingleDevice,
                 BluetoothDevice.BOND_NONE, badPriorityValue, false);
         testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
-        testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mSingleDevice,
                 BluetoothDevice.BOND_BONDING, badPriorityValue, false);
         testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
         testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
-        testOkToConnectCase(mSingleDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
         testOkToConnectCase(mSingleDevice,
                 BluetoothDevice.BOND_BONDED, badPriorityValue, false);
         testOkToConnectCase(mSingleDevice,
-                badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
+                badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToConnectCase(mSingleDevice,
-                badBondState, BluetoothProfile.PRIORITY_OFF, false);
+                badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mSingleDevice,
-                badBondState, BluetoothProfile.PRIORITY_ON, false);
-        testOkToConnectCase(mSingleDevice,
-                badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mSingleDevice,
                 badBondState, badPriorityValue, false);
     }
@@ -301,12 +286,14 @@
     public void testOutgoingConnectMissingHearingAidUuid() {
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -328,8 +315,9 @@
 
         // Set the device priority to PRIORITY_OFF so connect() should fail
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
 
         // Send a connect request
         Assert.assertFalse("Connect expected to fail", mService.connect(mLeftDevice));
@@ -342,12 +330,15 @@
     public void testOutgoingConnectTimeout() {
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -361,8 +352,8 @@
                 mService.getConnectionState(mLeftDevice));
 
         // Verify the connection state broadcast, and that we are in Disconnected state
-        verifyConnectionStateIntent(HearingAidStateMachine.sConnectTimeoutMs * 2, mLeftDevice,
-                BluetoothProfile.STATE_DISCONNECTED,
+        verifyConnectionStateIntent(HearingAidStateMachine.sConnectTimeoutMs * 2,
+                mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
                 BluetoothProfile.STATE_CONNECTING);
         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
                 mService.getConnectionState(mLeftDevice));
@@ -377,12 +368,15 @@
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -409,12 +403,15 @@
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -473,12 +470,15 @@
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -590,12 +590,15 @@
     public void testCreateStateMachineStackEvents() {
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -655,12 +658,15 @@
     public void testDeleteStateMachineUnbondEvents() {
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -716,12 +722,15 @@
     public void testDeleteStateMachineDisconnectEvents() {
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
@@ -765,12 +774,15 @@
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_OFF);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
 
         generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
                 BluetoothProfile.STATE_DISCONNECTED);
@@ -799,12 +811,15 @@
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
                 BluetoothProfile.STATE_DISCONNECTED);
@@ -832,10 +847,12 @@
     public void firstTimeConnection_shouldConnectToBothDevices() {
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
         // Send a connect request for left device
@@ -915,12 +932,15 @@
     public void getHiSyncId_afterFirstDeviceConnected() {
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
-        when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
         // Send a connect request
@@ -1007,8 +1027,8 @@
 
         // Update the device priority so okToConnect() returns true
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEARING_AID))
-                .thenReturn(BluetoothProfile.PRIORITY_ON);
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mNativeInterface).connectHearingAid(device);
         doReturn(true).when(mNativeInterface).disconnectHearingAid(device);
 
@@ -1076,7 +1096,7 @@
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEARING_AID))
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID))
                 .thenReturn(priority);
         Assert.assertEquals(expected, mService.okToConnect(device));
     }
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
index f32f96f..d148b66 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
@@ -125,13 +125,9 @@
     public void testListenForPhoneState_ServiceAndSignalStrength() {
         BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
         mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
-                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
         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);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
     }
 
     /**
@@ -142,19 +138,12 @@
     public void testListenForPhoneState_ServiceAndSignalStrengthUpdateTurnOffSignalStrengh() {
         BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
         mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
-                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
         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);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
         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);
     }
 
     /**
@@ -164,18 +153,11 @@
     public void testListenForPhoneState_ServiceAndSignalStrengthUpdateTurnOffAll() {
         BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
         mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
-                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
         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);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
         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);
     }
 
     /**
@@ -189,27 +171,17 @@
         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);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
         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);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
         // Enabling updates from second device should not trigger the same subscription
         mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_SERVICE_STATE
-                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
-        verifyNoMoreInteractions(mTelephonyManager);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
         // 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);
     }
 
     /**
@@ -226,34 +198,18 @@
         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);
+        mHeadsetPhoneState.listenForPhoneState(device2,
+                PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
         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);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
         // 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);
+        verify(mTelephonyManager).listen(
+                any(), eq(PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
         // 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
index ab8bdd6..149c64f 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -85,7 +85,7 @@
     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 ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.HFP};
     private static final String TEST_PHONE_NUMBER = "1234567890";
     private static final String TEST_CALLER_ID = "Test Name";
 
@@ -164,9 +164,8 @@
         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)
+        doReturn(new ParcelUuid[]{BluetoothUuid.HFP}).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
@@ -280,8 +279,8 @@
     public void testConnectFromApi() {
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mBondedDevices.add(device);
         Assert.assertTrue(mHeadsetService.connect(device));
         verify(mObjectsFactory).makeStateMachine(device,
@@ -324,8 +323,8 @@
     public void testUnbondDevice_disconnectBeforeUnbond() {
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mBondedDevices.add(device);
         Assert.assertTrue(mHeadsetService.connect(device));
         verify(mObjectsFactory).makeStateMachine(device,
@@ -368,8 +367,8 @@
     public void testUnbondDevice_disconnectAfterUnbond() {
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mBondedDevices.add(device);
         Assert.assertTrue(mHeadsetService.connect(device));
         verify(mObjectsFactory).makeStateMachine(device,
@@ -1154,8 +1153,8 @@
 
     private void connectTestDevice(BluetoothDevice device) {
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         // Make device bonded
         mBondedDevices.add(device);
         // Use connecting event to indicate that device is connecting
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
index 57a9a2f..465b78e 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
@@ -65,7 +65,7 @@
 @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 ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.HFP};
     private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250;
     private static final String TEST_PHONE_NUMBER = "1234567890";
 
@@ -100,9 +100,8 @@
                 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)
+        doReturn(new ParcelUuid[]{BluetoothUuid.HFP}).when(mAdapterService)
                 .getRemoteUuids(any(BluetoothDevice.class));
         // This line must be called to make sure relevant objects are initialized properly
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -150,7 +149,7 @@
         mHeadsetServiceBinder = (IBluetoothHeadset.Stub) mHeadsetService.initBinder();
         Assert.assertNotNull(mHeadsetServiceBinder);
         mHeadsetServiceBinder.setForceScoAudio(true);
-        // Mock database for getPriority()
+        // Mock database for getConnectionPolicy()
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
     }
 
@@ -194,43 +193,35 @@
         int badPriorityValue = 1024;
         int badBondState = 42;
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
-                BluetoothProfile.PRIORITY_UNDEFINED, false);
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
-                BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
-                BluetoothProfile.PRIORITY_ON, false);
-        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
-                BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE, badPriorityValue,
                 false);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
-                BluetoothProfile.PRIORITY_UNDEFINED, false);
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
-                BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
-                BluetoothProfile.PRIORITY_ON, false);
-        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
-                BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING, badPriorityValue,
                 false);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
-                BluetoothProfile.PRIORITY_UNDEFINED, true);
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
-                BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
-                BluetoothProfile.PRIORITY_ON, true);
-        testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
-                BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED, 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);
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToAcceptConnectionCase(mCurrentDevice, badBondState,
-                BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
+        testOkToAcceptConnectionCase(mCurrentDevice, badBondState,
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToAcceptConnectionCase(mCurrentDevice, badBondState, badPriorityValue, false);
     }
 
@@ -241,9 +232,9 @@
      */
     @Test
     public void testConnectDevice_connectDeviceBelowLimit() {
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
         Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
         verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
@@ -353,9 +344,9 @@
     @Test
     public void testConnectDevice_connectDeviceAboveLimit() {
         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
             mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
             Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -405,9 +396,9 @@
      */
     @Test
     public void testConnectAudio_withOneDevice() {
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
         Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
         verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
@@ -457,9 +448,9 @@
     @Test
     public void testConnectAudio_withMultipleDevices() {
         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
             mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
             Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -533,9 +524,9 @@
     @Test
     public void testConnectAudio_connectTwoAudioChannelsShouldFail() {
         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
             mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
             Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -605,9 +596,9 @@
     @Test
     public void testConnectAudio_firstConnectedAudioDevice() {
         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         doAnswer(invocation -> {
             BluetoothDevice[] devicesArray = new BluetoothDevice[connectedDevices.size()];
             return connectedDevices.toArray(devicesArray);
@@ -672,9 +663,9 @@
      */
     @Test
     public void testConnectAudio_deviceDisconnected() {
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
         HeadsetCallState headsetCallState =
                 new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
@@ -729,9 +720,9 @@
         HeadsetCallState headsetCallState =
                 new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
                         TEST_PHONE_NUMBER, 128, "");
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
         final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
         // Connect one device
@@ -788,9 +779,9 @@
                 new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
                         TEST_PHONE_NUMBER, 128, "");
         final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
             mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
             Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -846,9 +837,9 @@
      */
     @Test
     public void testSetSilenceMode() {
-        when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
-                .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         for (int i = 0; i < 2; i++) {
             mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
             Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -893,7 +884,7 @@
     private void testOkToAcceptConnectionCase(BluetoothDevice device, int bondState, int priority,
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
                 .thenReturn(priority);
         Assert.assertEquals(expected, mHeadsetService.okToAcceptConnection(device));
     }
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index 576d169..0dcb031 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -112,8 +112,8 @@
                 InstrumentationRegistry.getTargetContext().getResources());
         when(mHeadsetService.getPackageManager()).thenReturn(
                 InstrumentationRegistry.getContext().getPackageManager());
-        when(mHeadsetService.getPriority(any(BluetoothDevice.class))).thenReturn(
-                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         when(mHeadsetService.getForceScoAudio()).thenReturn(true);
         when(mHeadsetService.okToAcceptConnection(any(BluetoothDevice.class))).thenReturn(true);
         when(mHeadsetService.isScoAcceptable(any(BluetoothDevice.class))).thenReturn(true);
@@ -832,7 +832,7 @@
     public void testAtBiaEvent_initialSubscriptionWithUpdates() {
         setUpConnectedState();
         verify(mPhoneState).listenForPhoneState(mTestDevice, PhoneStateListener.LISTEN_SERVICE_STATE
-                | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+                | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
         mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA,
                         new HeadsetAgIndicatorEnableState(true, true, false, false), mTestDevice));
@@ -842,7 +842,7 @@
                 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);
+                PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
         mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA,
                         new HeadsetAgIndicatorEnableState(false, true, false, false), mTestDevice));
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
index b31c4e2..1ed8fe8 100644
--- a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
@@ -19,6 +19,7 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.espresso.intent.matcher.IntentMatchers;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
@@ -121,8 +122,8 @@
     @Test
     public void testIncomingPriorityReject() {
         // Return false for priority.
-        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
-                BluetoothProfile.PRIORITY_OFF);
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
 
         // Inject an event for when incoming connection is requested
         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
@@ -151,8 +152,8 @@
     @Test
     public void testIncomingPriorityAccept() {
         // Return true for priority.
-        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
-                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         // Inject an event for when incoming connection is requested
         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
@@ -197,8 +198,8 @@
     @Test
     public void testIncomingTimeout() {
         // Return true for priority.
-        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
-                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         // Inject an event for when incoming connection is requested
         StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
@@ -236,10 +237,11 @@
      */
     @LargeTest
     @Test
+    @FlakyTest
     public void testInBandRingtone() {
         // Return true for priority.
-        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
-                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing());
 
@@ -374,8 +376,8 @@
     /* Utility function: supported AT command should lead to native call */
     private void runSupportedVendorAtCommand(String atCommand, int vendorId) {
         // Return true for priority.
-        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
-                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         int expectedBroadcastIndex = 1;
 
@@ -417,8 +419,8 @@
     /* utility function: unsupported vendor specific command shall be filtered. */
     public void runUnsupportedVendorAtCommand(String atCommand, int vendorId) {
         // Return true for priority.
-        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
-                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
 
         int expectedBroadcastIndex = 1;
 
@@ -462,8 +464,8 @@
     private void runSupportedVendorEvent(int vendorId, String vendorEventCode,
             String vendorEventArgument) {
         // Setup connection state machine to be in connected state
-        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
-                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         int expectedBroadcastIndex = 1;
         expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
         expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
@@ -519,8 +521,8 @@
     public void runUnsupportedVendorEvent(int vendorId, String vendorEventCode,
             String vendorEventArgument) {
         // Setup connection state machine to be in connected state
-        when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
-                BluetoothProfile.PRIORITY_ON);
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         int expectedBroadcastIndex = 1;
         expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
         expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
index ede535d..c88fb75 100644
--- a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
@@ -97,48 +97,40 @@
         int badPriorityValue = 1024;
         int badBondState = 42;
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
-        testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mTestDevice,
                 BluetoothDevice.BOND_NONE, badPriorityValue, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
-        testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mTestDevice,
                 BluetoothDevice.BOND_BONDING, badPriorityValue, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
-        testOkToConnectCase(mTestDevice,
-                BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
         testOkToConnectCase(mTestDevice,
                 BluetoothDevice.BOND_BONDED, badPriorityValue, false);
         testOkToConnectCase(mTestDevice,
-                badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
+                badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
         testOkToConnectCase(mTestDevice,
-                badBondState, BluetoothProfile.PRIORITY_OFF, false);
+                badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
         testOkToConnectCase(mTestDevice,
-                badBondState, BluetoothProfile.PRIORITY_ON, false);
-        testOkToConnectCase(mTestDevice,
-                badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+                badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mTestDevice,
                 badBondState, badPriorityValue, false);
         // Restore prirority to undefined for this test device
-        Assert.assertTrue(mService.setPriority(
-                mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED));
+        Assert.assertTrue(mService.setConnectionPolicy(
+                mTestDevice, BluetoothProfile.CONNECTION_POLICY_UNKNOWN));
     }
 
     /**
@@ -153,7 +145,7 @@
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
         when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HID_HOST))
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HID_HOST))
                 .thenReturn(priority);
 
         // Test when the AdapterService is in non-quiet mode.
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
index bc25a11..53061a6 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.rule.ServiceTestRule;
 import androidx.test.runner.AndroidJUnit4;
@@ -105,7 +106,7 @@
      * @param priority - The priority value you want the device to have
      */
     private void mockDevicePriority(BluetoothDevice device, int priority) {
-        when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.MAP_CLIENT))
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT))
                 .thenReturn(priority);
     }
 
@@ -124,7 +125,7 @@
         Assert.assertNull(mService.getInstanceMap().get(device));
 
         // connect a bluetooth device
-        mockDevicePriority(device, BluetoothProfile.PRIORITY_ON);
+        mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         Assert.assertTrue(mService.connect(device));
 
         // is the statemachine created
@@ -137,13 +138,14 @@
      * Test that a PRIORITY_OFF device is not connected to
      */
     @Test
+    @FlakyTest
     public void testConnectPriorityOffDevice() {
         // make sure there is no statemachine already defined for this device
         BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
         Assert.assertNull(mService.getInstanceMap().get(device));
 
         // connect a bluetooth device
-        mockDevicePriority(device, BluetoothProfile.PRIORITY_OFF);
+        mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         Assert.assertFalse(mService.connect(device));
 
         // is the statemachine created
@@ -171,7 +173,7 @@
 
         // run the test - connect all devices, set their priorities to on
         for (BluetoothDevice d : list) {
-            mockDevicePriority(d, BluetoothProfile.PRIORITY_ON);
+            mockDevicePriority(d, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             Assert.assertTrue(mService.connect(d));
         }