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): mP